# -*- coding: utf-8 -*- 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 from traceback import print_exc # monkey patch bug in python 2.6 and lower # 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": import errno from subprocess import Popen def _eintr_retry_call(func, *args): while True: try: return func(*args) except OSError, e: if e.errno == errno.EINTR: continue raise # unsued timeout option for older python version def wait(self, timeout=0): """Wait for child process to terminate. Returns returncode attribute.""" if self.returncode is None: try: pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0) except OSError, e: if e.errno != errno.ECHILD: raise # This happens if SIGCLD is set to be ignored or waiting # for child processes has otherwise been disabled for our # process. This child is dead, we can't get the status. sts = 0 self._handle_exitstatus(sts) return self.returncode Popen.wait = wait if os.name != "nt": from grp import getgrnam from os import chown from pwd import getpwnam from pyload.plugins.internal.Addon import Addon, threaded, Expose from pyload.plugins.internal.AbstractExtractor import ArchiveError, CRCError, WrongPassword from pyload.utils import safe_join, fs_encode class ExtractArchive(Addon): __name__ = "ExtractArchive" __type__ = "addon" __version__ = "0.18" __config__ = [("activated" , "bool" , "Activated" , True ), ("fullpath" , "bool" , "Extract full path" , True ), ("overwrite" , "bool" , "Overwrite files" , True ), ("passwordfile" , "file" , "password file" , "archive_password.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 )] __description__ = """Extract different kind of archives""" __license__ = "GPLv3" __authors__ = [("RaNaN", "ranan@pyload.org"), ("AndroKev", None), ("Walter Purcaro", "vuolter@gmail.com")] event_list = ["allDownloadsProcessed"] def setup(self): self.plugins = [] self.passwords = [] names = [] for p in ("UnRar", "UnZip"): try: module = self.core.pluginManager.loadModule("internal", p) klass = getattr(module, p) if klass.checkDeps(): names.append(p) self.plugins.append(klass) except OSError, e: if e.errno == 2: self.logInfo(_("No %s installed") % p) else: self.logWarning(_("Could not activate %s") % p, e) if self.core.debug: print_exc() except Exception, e: self.logWarning(_("Could not activate %s") % p, e) if self.core.debug: print_exc() if names: self.logInfo(_("Activated") + " " + " ".join(names)) else: self.logInfo(_("No Extract plugins activated")) # 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(pid) else: self.manager.startThread(self.extract, [pid]) @threaded def allDownloadsProcessed(self, thread): local = copy(self.queue) del self.queue[:] 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 = [] destination = self.getConfig("destination") subfolder = self.getConfig("subfolder") fullpath = self.getConfig("fullpath") overwrite = self.getConfig("overwrite") excludefiles = self.getConfig("excludefiles") renice = self.getConfig("renice") recursive = self.getConfig("recursive") # reload from txt file self.reloadPasswords() # dl folder dl = self.config['general']['download_folder'] #iterate packages -> plugins -> targets for pid in ids: p = self.core.files.getPackage(pid) self.logInfo(_("Check package %s") % p.name) if not p: continue # determine output folder out = safe_join(dl, p.folder, "") out = safe_join(dl, p.folder, self.getConfig("destination"), "") if subfolder: out = safe_join(out, fs_encode(p.folder)) if not exists(out): makedirs(out) files_ids = [(safe_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 = [] 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 processed: self.logDebug(basename(target), "skipped") continue processed.append(target) # prevent extracting same file twice self.logInfo(basename(target), _("Extract to %s") % out) try: klass = plugin(self, target, out, fullpath, overwrite, excludefiles, renice) klass.init() password = p.password.strip().splitlines() new_files = self._extract(klass, fid, password, thread) except Exception, e: self.logError(basename(target), e) success = False continue self.logDebug("Extracted", new_files) self.setPermissions(new_files) for file in new_files: if not exists(file): self.logDebug("New file %s does not exists" % file) continue 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 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")) return True if not failed else False def _extract(self, plugin, fid, passwords, thread): pyfile = self.core.files.getFile(fid) deletearchive = self.getConfig("deletearchive") pyfile.setCustomStatus(_("extracting")) thread.addActive(pyfile) # keep this file until everything is done try: progress = lambda x: pyfile.setProgress(x) success = False if not plugin.checkArchive(): plugin.extract(progress) success = True else: self.logInfo(basename(plugin.file), _("Password protected")) self.logDebug("Passwords", passwords) pwlist = copy(self.getPasswords()) # remove already supplied pws from list (only local) for pw in passwords: if pw in pwlist: pwlist.remove(pw) for pw in passwords + pwlist: try: self.logDebug("Try password", pw) if plugin.checkPassword(pw): plugin.extract(progress, pw) self.addPassword(pw) success = True break except WrongPassword: self.logDebug("Password was wrong") if not success: raise Exception(_("Wrong password")) if self.core.debug: self.logDebug("Would delete", ", ".join(plugin.getDeleteFiles())) if deletearchive: files = plugin.getDeleteFiles() self.logInfo(_("Deleting %s files") % len(files)) for f in files: if exists(f): remove(f) else: self.logDebug("%s does not exists" % f) self.logInfo(basename(plugin.file), _("Extracting finished")) extracted_files = plugin.getExtractedFiles() self.manager.dispatchEvent("archive_extracted", pyfile, plugin.out, plugin.file, extracted_files) return extracted_files except ArchiveError, e: self.logError(basename(plugin.file), _("Archive Error"), e) except CRCError: self.logError(basename(plugin.file), _("CRC Mismatch")) except Exception, e: if self.core.debug: print_exc() self.logError(basename(plugin.file), _("Unknown Error"), e) self.manager.dispatchEvent("archive_extract_failed", pyfile) raise Exception(_("Extract failed")) @Expose def getPasswords(self): """ List of saved passwords """ return self.passwords def reloadPasswords(self): passwordfile = self.getConfig("passwordfile") 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 @Expose def addPassword(self, pw): """ Adds a password to saved list""" passwordfile = self.getConfig("passwordfile") if pw in self.passwords: self.passwords.remove(pw) self.passwords.insert(0, pw) try: with open(passwordfile, "wb") as f: for pw in self.passwords: f.write(pw + "\n") except IOError, e: self.logError(e) def setPermissions(self, files): for f in files: if not exists(f): continue try: if self.config['permission']['change_file']: if isfile(f): chmod(f, int(self.config['permission']['file'], 8)) elif isdir(f): 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] chown(f, uid, gid) except Exception, e: self.logWarning(_("Setting User and Group failed"), e)