#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import os from os import remove, chmod, makedirs from os.path import exists, basename, isfile, isdir, join 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 if sys.version_info < (2, 7) and os.name != "nt": from subprocess import Popen import errno 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 os import chown from pwd import getpwnam from grp import getgrnam from pyload.utils.fs import safe_join as save_join, fs_encode from pyload.plugins.Addon import Addon, threaded, AddonHandler from pyload.plugins.internal.AbstractExtractor import ArchiveError, CRCError, WrongPassword # TODO: plugin needs a rewrite to work on unfinished packages class ExtractArchive(Addon): """ Provides: unrarFinished (folder, filename) """ __name__ = "ExtractArchive" __version__ = "0.16" __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__ = ("adminpyload.org", "@pyloadforum") event_list = ["allDownloadsProcessed"] def init(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, str(e)) if self.core.debug: print_exc() except Exception, e: self.logWarning(_("Could not activate %s") % p, str(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 = [] @AddonHandler(_("Extract package"), _("Scans package for archives and extract them")) def extractPackage(self, pid): """ Extract package with given id""" self.manager.startThread(self.extract, [pid]) def packageFinished(self, pypack): if self.getConfig("queue"): self.logInfo(_("Package %s queued for later extracting") % pypack.name) self.queue.append(pypack.id) else: self.manager.startThread(self.extract, [pypack.id]) @threaded def allDownloadsProcessed(self, thread): local = copy(self.queue) del self.queue[:] self.extract(local, thread) def extract(self, ids, thread=None): # 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) if not p: continue # determine output folder out = save_join(dl, p.folder, "") # force trailing slash if self.getConfig("destination") and self.getConfig("destination").lower() != "none": out = save_join(dl, p.folder, self.getConfig("destination"), "") #relative to package folder if destination is relative, otherwise absolute path overwrites them if self.getConfig("subfolder"): out = join(out, fs_encode(p.folder)) if not exists(out): makedirs(out) files_ids = [(save_join(dl, p.folder, f.name), f.fid) for f in p.getFiles().itervalues()] matched = False # 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 extracted: 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() 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.setPermissions(new_files) for file in new_files: if not exists(file): self.logDebug("new file %s does not exists" % file) continue if self.getConfig("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: self.logInfo(_("No files found to extract")) def startExtracting(self, plugin, fid, passwords, 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: thread.setProgress(x) success = False if not plugin.checkArchive(): 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) for pw in passwords + pwlist: try: self.logDebug("Try password: %s" % 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: self.logError(basename(plugin.file), _("Wrong password")) return [] if self.core.debug: self.logDebug("Would delete: %s" % ", ".join(plugin.getDeleteFiles())) if self.getConfig("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")) self.manager.dispatchEvent("extracting:finished", plugin.out, plugin.file) return plugin.getExtractedFiles() except ArchiveError, e: self.logError(basename(plugin.file), _("Archive Error"), str(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"), str(e)) return [] # TODO: config handler for passwords? def getPasswords(self): """ List of saved passwords """ return self.passwords def addPassword(self, pw): """ Adds a password to saved list""" pwfile = self.getConfig("passwordfile") if pw in self.passwords: self.passwords.remove(pw) self.passwords.insert(0, pw) f = open(pwfile, "wb") for pw in self.passwords: f.write(pw + "\n") f.close() def reloadPasswords(self): pwfile = self.getConfig("passwordfile") if not exists(pwfile): open(pwfile, "wb").close() passwords = [] f = open(pwfile, "rb") for pw in f.read().splitlines(): passwords.append(pw) f.close() self.passwords = passwords 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)