diff options
Diffstat (limited to 'module/plugins/hooks/ExtractArchive.py')
-rw-r--r-- | module/plugins/hooks/ExtractArchive.py | 350 |
1 files changed, 237 insertions, 113 deletions
diff --git a/module/plugins/hooks/ExtractArchive.py b/module/plugins/hooks/ExtractArchive.py index 12e53fe50..af78ffc93 100644 --- a/module/plugins/hooks/ExtractArchive.py +++ b/module/plugins/hooks/ExtractArchive.py @@ -1,19 +1,20 @@ # -*- coding: utf-8 -*- -import sys +from __future__ import with_statement + import os +import sys + +from copy import copy from os import remove, chmod, makedirs -from os.path import exists, basename, isfile, isdir, join +from os.path import exists, basename, isfile, isdir from traceback import print_exc -from copy import copy # monkey patch bug in python 2.6 and lower -# see http://bugs.python.org/issue6122 -# http://bugs.python.org/issue1236 -# http://bugs.python.org/issue1731717 +# http://bugs.python.org/issue6122 , http://bugs.python.org/issue1236 , http://bugs.python.org/issue1731717 if sys.version_info < (2, 7) and os.name != "nt": - from subprocess import Popen import errno + from subprocess import Popen def _eintr_retry_call(func, *args): while True: @@ -24,6 +25,7 @@ if sys.version_info < (2, 7) and os.name != "nt": continue raise + # unsued timeout option for older python version def wait(self, timeout=0): """Wait for child process to terminate. Returns returncode @@ -44,44 +46,54 @@ if sys.version_info < (2, 7) and os.name != "nt": Popen.wait = wait if os.name != "nt": + from grp import getgrnam from os import chown from pwd import getpwnam - from grp import getgrnam -from module.utils import save_join, fs_encode from module.plugins.Hook import Hook, threaded, Expose -from module.plugins.internal.AbstractExtractor import ArchiveError, CRCError, WrongPassword +from module.plugins.internal.Extractor import ArchiveError, CRCError, PasswordError +from module.utils import save_join, uniqify class ExtractArchive(Hook): - """ - Provides: unrarFinished (folder, filename) - """ - __name__ = "ExtractArchive" - __version__ = "0.16" + __name__ = "ExtractArchive" + __type__ = "hook" + __version__ = "1.02" + + __config__ = [("activated" , "bool" , "Activated" , True ), + ("fullpath" , "bool" , "Extract full path" , True ), + ("overwrite" , "bool" , "Overwrite files" , False ), + ("keepbroken" , "bool" , "Extract broken archives" , False ), + ("repair" , "bool" , "Repair broken archives" , True ), + ("passwordfile" , "file" , "Store passwords in file" , "archive_password.txt" ), + ("delete" , "bool" , "Delete archive when successfully extracted", False ), + ("subfolder" , "bool" , "Create subfolder for each package" , False ), + ("destination" , "folder", "Extract files to" , "" ), + ("extensions" , "str" , "Extract the following extensions" , "7z,bz2,bzip2,gz,gzip,lha,lzh,lzma,rar,tar,taz,tbz,tbz2,tgz,xar,xz,z,zip"), + ("excludefiles" , "str" , "Don't extract the following files" , "*.nfo,*.DS_Store,index.dat,thumb.db" ), + ("recursive" , "bool" , "Extract archives in archives" , True ), + ("queue" , "bool" , "Wait for all downloads to be finished" , True ), + ("renice" , "int" , "CPU Priority" , 0 )] + __description__ = """Extract different kind of archives""" - __config__ = [("activated", "bool", "Activated", True), - ("fullpath", "bool", "Extract full path", True), - ("overwrite", "bool", "Overwrite files", True), - ("passwordfile", "file", "password file", "unrar_passwords.txt"), - ("deletearchive", "bool", "Delete archives when done", False), - ("subfolder", "bool", "Create subfolder for each package", False), - ("destination", "folder", "Extract files to", ""), - ("excludefiles", "str", "Exclude files from unpacking (seperated by ;)", ""), - ("recursive", "bool", "Extract archives in archvies", True), - ("queue", "bool", "Wait for all downloads to be finished", True), - ("renice", "int", "CPU Priority", 0)] - __author_name__ = ("pyload Team", "AndroKev") - __author_mail__ = ("admin<at>pyload.org", "@pyloadforum") + __license__ = "GPLv3" + __authors__ = [("Walter Purcaro", "vuolter@gmail.com")] + event_list = ["allDownloadsProcessed"] + + #@TODO: Remove in 0.4.10 + def initPeriodical(self): + pass + + def setup(self): - self.plugins = [] + self.plugins = [] self.passwords = [] names = [] - for p in ("UnRar", "UnZip"): + for p in ("UnRar", "SevenZip", "UnZip"): try: module = self.core.pluginManager.loadModule("internal", p) klass = getattr(module, p) @@ -93,12 +105,12 @@ class ExtractArchive(Hook): if e.errno == 2: self.logInfo(_("No %s installed") % p) else: - self.logWarning(_("Could not activate %s") % p, str(e)) + self.logWarning(_("Could not activate %s") % p, e) if self.core.debug: print_exc() except Exception, e: - self.logWarning(_("Could not activate %s") % p, str(e)) + self.logWarning(_("Could not activate %s") % p, e) if self.core.debug: print_exc() @@ -110,138 +122,230 @@ class ExtractArchive(Hook): # queue with package ids self.queue = [] + @Expose def extractPackage(self, id): """ Extract package with given id""" self.manager.startThread(self.extract, [id]) + def packageFinished(self, pypack): + pid = pypack.id if self.getConfig("queue"): self.logInfo(_("Package %s queued for later extracting") % pypack.name) - self.queue.append(pypack.id) + self.queue.append(pid) else: - self.manager.startThread(self.extract, [pypack.id]) + self.extractPackage(pid) + @threaded def allDownloadsProcessed(self, thread): local = copy(self.queue) + del self.queue[:] - self.extract(local, thread) + + if self.extract(local, thread): #: check only if all gone fine, no failed reporting for now + self.manager.dispatchEvent("all_archives_extracted") + + self.manager.dispatchEvent("all_archives_processed") + def extract(self, ids, thread=None): + processed = [] + extracted = [] + failed = [] + + clearlist = lambda string: [x.lstrip('.') for x in string.replace(' ', '').replace(',', '|').replace(';', '|').split('|')] + + destination = self.getConfig("destination") + subfolder = self.getConfig("subfolder") + fullpath = self.getConfig("fullpath") + overwrite = self.getConfig("overwrite") + extensions = clearlist(self.getConfig("extensions")) + excludefiles = clearlist(self.getConfig("excludefiles")) + renice = self.getConfig("renice") + recursive = self.getConfig("recursive") + delete = self.getConfig("delete") + keepbroken = self.getConfig("keepbroken") + + if extensions: + self.logDebug("Extensions allowed: %s" % "|.".join(extensions)) + # reload from txt file self.reloadPasswords() # dl folder dl = self.config['general']['download_folder'] - extracted = [] - #iterate packages -> plugins -> targets for pid in ids: p = self.core.files.getPackage(pid) - self.logInfo(_("Check package %s") % p.name) + self.logInfo(_("Check package: %s") % p.name) if not p: continue # determine output folder - out = save_join(dl, p.folder, "") - # force trailing slash + out = save_join(dl, p.folder, destination, "") #: force trailing slash - if self.getConfig("destination") and self.getConfig("destination").lower() != "none": + if subfolder: + out = save_join(out, p.folder) - out = save_join(dl, p.folder, self.getConfig("destination"), "") - #relative to package folder if destination is relative, otherwise absolute path overwrites them + if not exists(out): + makedirs(out) - if self.getConfig("subfolder"): - out = join(out, fs_encode(p.folder)) - - if not exists(out): - makedirs(out) - - files_ids = [(save_join(dl, p.folder, x["name"]), x["id"]) for x in p.getChildren().itervalues()] - matched = False + files_ids = [(save_join(dl, p.folder, x['name']), x['id']) for x in p.getChildren().itervalues()] + matched = False + success = True # check as long there are unseen files while files_ids: new_files_ids = [] + if extensions: + files_ids = [(file, id) for file, id in files_ids if filter(lambda ext: file.endswith(ext), extensions)] + for plugin in self.plugins: targets = plugin.getTargets(files_ids) + if targets: self.logDebug("Targets for %s: %s" % (plugin.__name__, targets)) matched = True + for target, fid in targets: - if target in extracted: + if target in processed: self.logDebug(basename(target), "skipped") continue - extracted.append(target) # prevent extracting same file twice - klass = plugin(self, target, out, self.getConfig("fullpath"), self.getConfig("overwrite"), self.getConfig("excludefiles"), - self.getConfig("renice")) - klass.init() + processed.append(target) # prevent extracting same file twice + + self.logInfo(basename(target), _("Extract to: %s") % out) + try: + klass = plugin(self, + target, + out, + p.password, + fullpath, + overwrite, + excludefiles, + renice, + delete, + keepbroken) + klass.init() + + new_files = self._extract(klass, fid, thread) + + except Exception, e: + self.logError(basename(target), e) + new_files = None + + if new_files is None: + self.logWarning(basename(target), _("No files extracted")) + success = False + continue - self.logInfo(basename(target), _("Extract to %s") % out) - new_files = self.startExtracting(klass, fid, p.password.strip().splitlines(), thread) - self.logDebug("Extracted: %s" % new_files) + self.logDebug("Extracted files: %s" % new_files) self.setPermissions(new_files) for file in new_files: if not exists(file): - self.logDebug("new file %s does not exists" % file) + self.logDebug("New file %s does not exists" % file) continue - if self.getConfig("recursive") and isfile(file): + if recursive and isfile(file): new_files_ids.append((file, fid)) # append as new target files_ids = new_files_ids # also check extracted files - if not matched: + if matched: + if success: + extracted.append(pid) + self.manager.dispatchEvent("package_extracted", p) + else: + failed.append(pid) + self.manager.dispatchEvent("package_extract_failed", p) + else: self.logInfo(_("No files found to extract")) - def startExtracting(self, plugin, fid, passwords, thread): + if not matched or not success and subfolder: + try: + os.rmdir(out) + except OSError: + pass + + return True if not failed else False + + + def _extract(self, plugin, fid, thread): pyfile = self.core.files.getFile(fid) - if not pyfile: - return [] pyfile.setCustomStatus(_("extracting")) thread.addActive(pyfile) # keep this file until everything is done try: - progress = lambda x: pyfile.setProgress(x) - success = False + progress = lambda x: pyfile.setProgress(x) + encrypted = False + passwords = self.getPasswords() + + try: + self.logInfo(basename(plugin.file), "Verifying...") + + tmp_password = plugin.password + plugin.password = "" #: Force verifying without password + + plugin.verify() + + except PasswordError: + encrypted = True + + except CRCError: + self.logWarning(basename(plugin.file), _("Archive damaged")) + + if not self.getConfig("repair"): + raise CRCError + + elif plugin.repair(): + self.logInfo(basename(plugin.file), _("Successfully repaired")) + + elif not self.getConfig("keepbroken"): + raise ArchiveError(_("Broken archive")) - if not plugin.checkArchive(): + else: + self.logInfo(basename(plugin.file), _("All OK")) + + plugin.password = tmp_password + + if not encrypted: plugin.extract(progress) - success = True + else: self.logInfo(basename(plugin.file), _("Password protected")) - self.logDebug("Passwords: %s" % str(passwords)) - pwlist = copy(self.getPasswords()) - #remove already supplied pws from list (only local) - for pw in passwords: - if pw in pwlist: - pwlist.remove(pw) + if plugin.password: + passwords.insert(0, plugin.password) + passwords = uniqify(self.passwords) + self.logDebug("Password: %s" % plugin.password) + else: + self.logDebug("No package password provided") - for pw in passwords + pwlist: + for pw in passwords: try: self.logDebug("Try password: %s" % pw) - if plugin.checkPassword(pw): - plugin.extract(progress, pw) + + if plugin.setPassword(pw): + plugin.extract(progress) self.addPassword(pw) - success = True break - except WrongPassword: - self.logDebug("Password was wrong") + else: + raise PasswordError - if not success: - self.logError(basename(plugin.file), _("Wrong password")) - return [] + except PasswordError: + self.logDebug("Password was wrong") + else: + raise PasswordError if self.core.debug: self.logDebug("Would delete: %s" % ", ".join(plugin.getDeleteFiles())) - if self.getConfig("deletearchive"): + if self.getConfig("delete"): files = plugin.getDeleteFiles() self.logInfo(_("Deleting %s files") % len(files)) for f in files: @@ -251,67 +355,87 @@ class ExtractArchive(Hook): self.logDebug("%s does not exists" % f) self.logInfo(basename(plugin.file), _("Extracting finished")) - self.manager.dispatchEvent("unrarFinished", plugin.out, plugin.file) - return plugin.getExtractedFiles() + extracted_files = plugin.getExtractedFiles() + self.manager.dispatchEvent("archive_extracted", pyfile, plugin.out, plugin.file, extracted_files) + + return extracted_files + + except PasswordError: + self.logError(basename(plugin.file), _("Wrong password" if passwords else "No password found")) + plugin.password = "" - except ArchiveError, e: - self.logError(basename(plugin.file), _("Archive Error"), str(e)) except CRCError: self.logError(basename(plugin.file), _("CRC Mismatch")) + + except ArchiveError, e: + self.logError(basename(plugin.file), _("Archive Error"), e) + except Exception, e: if self.core.debug: print_exc() - self.logError(basename(plugin.file), _("Unknown Error"), str(e)) + self.logError(basename(plugin.file), _("Unknown Error"), e) + + self.manager.dispatchEvent("archive_extract_failed", pyfile) + + self.logError(basename(plugin.file), _("Extract failed")) - return [] @Expose def getPasswords(self): """ List of saved passwords """ return self.passwords + def reloadPasswords(self): - pwfile = self.getConfig("passwordfile") - if not exists(pwfile): - open(pwfile, "wb").close() + passwordfile = self.getConfig("passwordfile") - passwords = [] - f = open(pwfile, "rb") - for pw in f.read().splitlines(): - passwords.append(pw) - f.close() + try: + passwords = [] + with open(passwordfile, "a+") as f: + for pw in f.read().splitlines(): + passwords.append(pw) + + except IOError, e: + self.logError(e) + + else: + self.passwords = passwords - self.passwords = passwords @Expose def addPassword(self, pw): """ Adds a password to saved list""" - pwfile = self.getConfig("passwordfile") + passwordfile = self.getConfig("passwordfile") - if pw in self.passwords: - self.passwords.remove(pw) self.passwords.insert(0, pw) + self.passwords = uniqify(self.passwords) + + try: + with open(passwordfile, "wb") as f: + for pw in self.passwords: + f.write(pw + '\n') + + except IOError, e: + self.logError(e) - f = open(pwfile, "wb") - for pw in self.passwords: - f.write(pw + "\n") - f.close() def setPermissions(self, files): for f in files: if not exists(f): continue + try: - if self.config["permission"]["change_file"]: + if self.config['permission']['change_file']: if isfile(f): - chmod(f, int(self.config["permission"]["file"], 8)) + chmod(f, int(self.config['permission']['file'], 8)) elif isdir(f): - chmod(f, int(self.config["permission"]["folder"], 8)) + chmod(f, int(self.config['permission']['folder'], 8)) - if self.config["permission"]["change_dl"] and os.name != "nt": - uid = getpwnam(self.config["permission"]["user"])[2] - gid = getgrnam(self.config["permission"]["group"])[2] + if self.config['permission']['change_dl'] and os.name != "nt": + uid = getpwnam(self.config['permission']['user'])[2] + gid = getgrnam(self.config['permission']['group'])[2] chown(f, uid, gid) + except Exception, e: self.logWarning(_("Setting User and Group failed"), e) |