From c901c2724613f9c977a83902ce41564c8205d57b Mon Sep 17 00:00:00 2001 From: gen2brain Date: Sat, 6 Apr 2013 11:08:40 +0200 Subject: [PATCH] support multiple files in archives --- src/m64py/archive.py | 114 +++++++++++++++++++--------------- src/m64py/frontend/dialogs.py | 39 +++++++----- src/m64py/frontend/romlist.py | 49 +++++++-------- 3 files changed, 110 insertions(+), 92 deletions(-) diff --git a/src/m64py/archive.py b/src/m64py/archive.py index c54b9ea..e305fc1 100644 --- a/src/m64py/archive.py +++ b/src/m64py/archive.py @@ -15,7 +15,6 @@ # along with this program. If not, see . import os -import sys import bz2 import gzip import zipfile @@ -23,12 +22,7 @@ import shutil import tempfile from subprocess import Popen, PIPE -try: - from m64py.utils import which -except ImportError, err: - sys.stderr.write("Error: Can't import m64py modules%s%s%s" % ( - os.linesep, str(err), os.linesep)) - sys.exit(1) +from m64py.utils import which try: import UnRAR2 @@ -64,12 +58,11 @@ class Archive(): def __init__(self, filename): """Opens archive.""" self.file = os.path.realpath(filename) - if not os.path.isfile(self.file) \ - or not os.access(self.file, os.R_OK): - raise IOError("Cannot open %s. No such file." % ( - self.file)) + if not os.path.isfile(self.file) or not os.access(self.file, os.R_OK): + raise IOError("Cannot open %s. No such file." % (self.file)) self.filetype = self.get_filetype() + if self.filetype == ZIP: self.fd = zipfile.ZipFile(self.file, 'r') elif self.filetype == GZIP: @@ -84,41 +77,39 @@ class Archive(): elif RAR_CMD: self.fd = RarCmd(self.file) else: - raise IOError("UnRAR2 module or rar/unrar is needed for %s." % ( - self.file)) + raise IOError("UnRAR2 module or rar/unrar is needed for %s." % (self.file)) elif self.filetype == LZMA: if HAS_7Z: self.fd = Archive7z(open(self.file, 'rb')) elif LZMA_CMD: self.fd = LzmaCmd(self.file) else: - raise IOError("lzma module or 7z is needed for %s." % ( - self.file)) + raise IOError("lzma module or 7z is needed for %s." % (self.file)) else: - raise IOError("File %s is not a N64 ROM file." % ( - self.file)) + raise IOError("File %s is not a N64 ROM file." % (self.file)) - def read(self): - """Reads data. If archive has more then one - file the first one is used.""" + self.namelist = self.get_namelist() + + def read(self, filename=None): + """Reads data.""" data = None + fname = self.namelist[0] if not filename else filename if self.filetype == ZIP: - data = self.fd.read(self.fd.infolist()[0]) + data = self.fd.read(fname) elif self.filetype == GZIP: data = self.fd.read() elif self.filetype == BZIP: data = self.fd.read() elif self.filetype == RAR: if HAS_RAR: - data = self.fd.read_files()[0][1] + data = self.fd.read_files(fname)[0][1] elif RAR_CMD: - data = self.fd.read() + data = self.fd.read(fname) elif self.filetype == LZMA: if HAS_7Z: - data = self.fd.getmember( - self.fd.getnames()[0]).read() + data = self.fd.getmember(fname).read() elif LZMA_CMD: - data = self.fd.read() + data = self.fd.read(fname) elif self.filetype == ROM: data = self.fd.read() return data @@ -131,6 +122,29 @@ class Archive(): else: self.fd.close() + def get_namelist(self): + """Gets list of files in archive.""" + if self.filetype == ZIP: + namelist = [name.filename for name in self.fd.infolist()] + elif self.filetype == GZIP: + namelist = [os.path.basename(self.file)] + elif self.filetype == BZIP: + namelist = [os.path.basename(self.file)] + elif self.filetype == RAR: + if HAS_RAR: + for filename in self.fd.infoiter(): + namelist.append(filename.filename) + elif RAR_CMD: + namelist = self.fd.namelist + elif self.filetype == LZMA: + if HAS_7Z: + namelist = self.fd.getnames() + elif LZMA_CMD: + namelist = self.fd.namelist + elif self.filetype == ROM: + namelist = [os.path.basename(self.file)] + return namelist + def get_filetype(self): """Gets archive type.""" fd = open(self.file, 'rb') @@ -155,13 +169,10 @@ class RarCmd: def __init__(self, archive): """Opens archive.""" + self.fd = None self.file = archive self.namelist = self.namelist() - self.filename = self.namelist[0] - self.tempdir = tempfile.mkdtemp(self.file) - self.extract() - self.fd = open(os.path.join( - self.tempdir, self.filename), "rb") + self.tempdir = tempfile.mkdtemp() def namelist(self): """Returns list of filenames in archive.""" @@ -170,21 +181,24 @@ class RarCmd: def extract(self): """Extracts archive to temp dir.""" - proc = Popen([RAR_CMD, 'x', '-kb', '-p-', '-o-', '-inul', '--', - self.file, self.filename, self.tempdir], - stdin=PIPE, stdout=PIPE, stderr=PIPE) + cmd = [RAR_CMD, 'x', '-kb', '-p-', '-o-', '-inul', '--', + self.file, self.filename, self.tempdir] + proc = Popen(cmd, stdout=PIPE, stderr=PIPE) out = proc.communicate() if out[1] != '': - raise IOError("Error extracting file %s: %s." % ( - self.file, out[1])) + raise IOError("Error extracting file %s: %s." % (self.file, out[1])) - def read(self): + def read(self, filename=None): """Reads data.""" + self.filename = self.namelist[0] if not filename else filename + self.extract() + self.fd = open(os.path.join(self.tempdir, self.filename), "rb") return self.fd.read() def close(self): """Closes file descriptor and clean resources.""" - self.fd.close() + if self.fd: + self.fd.close() shutil.rmtree(self.tempdir) class LzmaCmd: @@ -192,35 +206,35 @@ class LzmaCmd: def __init__(self, archive): """Opens archive.""" + self.fd = None self.file = archive self.namelist = self.namelist() - self.filename = self.namelist[0] - self.tempdir = tempfile.mkdtemp(self.filename) - self.extract() - self.fd = open(os.path.join( - self.tempdir, self.filename), "rb") + self.tempdir = tempfile.mkdtemp() def namelist(self): """Returns list of filenames in archive.""" - proc1 = Popen([LZMA_CMD, 'l', self.file], stdout=PIPE) - proc2 = Popen(['grep', '-F', '...A'], stdin=proc1.stdout, stdout=PIPE) - lines = [name.rstrip(os.linesep) for name in proc2.stdout.readlines()] + proc = Popen([LZMA_CMD, 'l', self.file], stdout=PIPE) + lines = [name.rstrip(os.linesep) for name in proc.stdout.readlines() if '...A' in name] return [name[53:] for name in lines] def extract(self): """Extracts archive to temp dir.""" - proc = Popen([LZMA_CMD, 'x', '-o'+self.tempdir, self.file, self.filename], - stdin=PIPE, stdout=PIPE, stderr=PIPE) + cmd = [LZMA_CMD, 'x', '-o'+self.tempdir, self.file, self.filename] + proc = Popen(cmd, stdout=PIPE, stderr=PIPE) out = proc.communicate() if "Error" in out[0]: raise IOError("Error extracting file %s: %s." % ( self.file, out[0])) - def read(self): + def read(self, filename=None): """Reads data.""" + self.filename = self.namelist[0] if not filename else filename + self.extract() + self.fd = open(os.path.join(self.tempdir, self.filename), "rb") return self.fd.read() def close(self): """Closes file descriptor and clean resources.""" - self.fd.close() + if self.fd: + self.fd.close() shutil.rmtree(self.tempdir) diff --git a/src/m64py/frontend/dialogs.py b/src/m64py/frontend/dialogs.py index bc473a5..f4b2850 100644 --- a/src/m64py/frontend/dialogs.py +++ b/src/m64py/frontend/dialogs.py @@ -14,28 +14,23 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os -import sys +from PyQt4.QtCore import * +from PyQt4.QtGui import QDialog, QMessageBox, QListWidgetItem -from PyQt4.QtGui import QDialog, QMessageBox - -try: - from m64py.core.defs import FRONTEND_VERSION - from m64py.ui.about_ui import Ui_AboutDialog - from m64py.ui.license_ui import Ui_LicenseDialog -except ImportError, err: - sys.stderr.write("Error: Can't import m64py modules%s%s%s" % ( - os.linesep, str(err), os.linesep)) - sys.exit(1) +from m64py.utils import version_split +from m64py.core.defs import FRONTEND_VERSION +from m64py.ui.about_ui import Ui_AboutDialog +from m64py.ui.license_ui import Ui_LicenseDialog +from m64py.ui.archive_ui import Ui_ArchiveDialog class AboutDialog(QDialog, Ui_AboutDialog): def __init__(self, parent): QDialog.__init__(self, parent) self.setupUi(self) text = self.labelAbout.text() - text.replace("FRONTEND_VERSION", FRONTEND_VERSION) - text.replace("CORE_VERSION", - str(parent.worker.m64p.core_version)) + text = text.replace("FRONTEND_VERSION", FRONTEND_VERSION) + text = text.replace("CORE_VERSION", + version_split(parent.worker.m64p.core_version)) self.labelAbout.setText(text) self.show() @@ -51,3 +46,17 @@ class InfoDialog(QMessageBox): self.setText(text) self.setWindowTitle("Info") self.show() + +class ArchiveDialog(QDialog, Ui_ArchiveDialog): + def __init__(self, parent, files): + QDialog.__init__(self, parent) + self.setupUi(self) + self.build_list(files) + + def build_list(self, files): + self.listWidget.clear() + for fname in files: + item = QListWidgetItem(fname) + item.setData(Qt.UserRole, fname) + self.listWidget.addItem(item) + self.listWidget.setCurrentRow(0) diff --git a/src/m64py/frontend/romlist.py b/src/m64py/frontend/romlist.py index 15bb459..752c356 100644 --- a/src/m64py/frontend/romlist.py +++ b/src/m64py/frontend/romlist.py @@ -15,21 +15,15 @@ # along with this program. If not, see . import os -import sys import fnmatch import ConfigParser from PyQt4.QtGui import * from PyQt4.QtCore import * -try: - from m64py.utils import log, md5sum - from m64py.archive import Archive, EXT_FILTER - from m64py.ui.romlist_ui import Ui_ROMList -except ImportError, err: - sys.stderr.write("Error: Can't import m64py modules%s%s%s" % ( - os.linesep, str(err), os.linesep)) - sys.exit(1) +from m64py.utils import log, md5sum +from m64py.archive import Archive, EXT_FILTER +from m64py.ui.romlist_ui import Ui_ROMList try: from m64py.ui import title_rc @@ -75,8 +69,8 @@ class ROMList(QMainWindow, Ui_ROMList): def init(self): self.read_rom_list() - self.roms = self.qset.value("rom_list", []).toPyObject() - if self.qset.value("show_available", 0).toInt()[0]: + self.roms = self.qset.value("rom_list", []) + if bool(self.qset.value("show_available", 0)): self.add_available_items(self.roms) else: self.add_items() @@ -119,7 +113,7 @@ class ROMList(QMainWindow, Ui_ROMList): except KeyError: md5 = key list_item = QListWidgetItem(rom['goodname']) - list_item.setData(Qt.UserRole, (md5, None)) + list_item.setData(Qt.UserRole, (md5, None, None)) list_item.setFlags(Qt.ItemIsEnabled) self.listWidget.addItem(list_item) self.pushOpen.setEnabled(False) @@ -135,11 +129,11 @@ class ROMList(QMainWindow, Ui_ROMList): self.qset.setValue("rom_list", self.roms) self.qset.sync() self.listWidget.clear() - for md5, path in self.roms: + for md5, path, fname in self.roms: if md5 in self.romlist: goodname = self.romlist[md5]['goodname'] list_item = QListWidgetItem(goodname) - list_item.setData(Qt.UserRole, (md5, path)) + list_item.setData(Qt.UserRole, (md5, path, fname)) self.listWidget.addItem(list_item) self.progressBar.setValue(0) self.progressBar.hide() @@ -150,7 +144,7 @@ class ROMList(QMainWindow, Ui_ROMList): def refresh_items(self): """Refreshes available ROMs list""" - path_roms = self.qset.value("Paths/ROM").toString() + path_roms = self.qset.value("Paths/ROM") if not path_roms: self.parent.emit(SIGNAL( "info_dialog(PyQt_PyObject)"), @@ -162,31 +156,31 @@ class ROMList(QMainWindow, Ui_ROMList): self.reader.set_path(path_roms) self.reader.start() - def file_open(self, path): + def file_open(self, path, fname): """Opens ROM file.""" self.close() if self.parent.isMinimized(): self.parent.activateWindow() self.parent.emit(SIGNAL( - "file_open(PyQt_PyObject)"), str(path)) + "file_open(PyQt_PyObject, PyQt_PyObject)"), path, fname) def on_progress_bar_changed(self, value): self.progressBar.setValue(value) def on_item_open(self): item = self.listWidget.currentItem() - md5, path = item.data(Qt.UserRole).toPyObject() + md5, path, fname = item.data(Qt.UserRole) if path: - self.file_open(path) + self.file_open(path, fname) def on_item_activated(self, item): - md5, path = item.data(Qt.UserRole).toPyObject() + md5, path, fname = item.data(Qt.UserRole) if path: - self.file_open(path) + self.file_open(path, fname) def on_item_changed(self, current, previous): if not current: return - md5, path = current.data(Qt.UserRole).toPyObject() + md5, path, fname = current.data(Qt.UserRole) title = QPixmap(os.path.join( self.user_data_path, "title", "%s.png") % md5) @@ -241,7 +235,7 @@ class ROMReader(QThread): def set_path(self, path): """Sets ROM directory path.""" - self.rom_path = str(path) + self.rom_path = path def get_roms(self): """Returns ROM list.""" @@ -265,13 +259,14 @@ class ROMReader(QThread): fullpath = os.path.join(self.rom_path, filename) try: archive = Archive(fullpath) - romfile = archive.read() - archive.close() + for fname in archive.namelist: + romfile = archive.read(fname) + archive.close() + rom_md5 = md5sum(filedata=romfile) + self.roms.append((rom_md5.upper(), fullpath, fname)) except Exception, err: log.warn(str(err)) continue - rom_md5 = md5sum(filedata=romfile) - self.roms.append((rom_md5.upper(), fullpath)) percent = float(filenum) / float(num_files) * 100 self.parent.progressBar.emit( SIGNAL("valueChanged(int)"), percent)