diff options
Diffstat (limited to 'module')
| -rw-r--r-- | module/FileDatabase.py | 8 | ||||
| -rw-r--r-- | module/HookManager.py | 2 | ||||
| -rw-r--r-- | module/PluginThread.py | 12 | ||||
| -rw-r--r-- | module/plugins/Account.py | 2 | ||||
| -rw-r--r-- | module/plugins/hooks/UnRar.py | 124 | ||||
| -rw-r--r-- | module/pyunrar.py | 370 | 
6 files changed, 509 insertions, 9 deletions
| diff --git a/module/FileDatabase.py b/module/FileDatabase.py index d45763c05..4757831e7 100644 --- a/module/FileDatabase.py +++ b/module/FileDatabase.py @@ -435,7 +435,7 @@ class FileHandler:          ids = self.db.getUnfinished(pyfile.packageid)          if not ids or (pyfile.id in ids and len(ids) == 1):              if not pyfile.package().setFinished: -                self.core.log.info("Package finished: %s" % pyfile.package().name) +                self.core.log.info(_("Package finished: %s") % pyfile.package().name)                  self.core.hookManager.packageFinished(pyfile.package())                  pyfile.package().setFinished = True @@ -827,6 +827,9 @@ class PyFile():          self.active = False #obsolete?          self.abort = False          self.reconnected = False +         +        #hook progress +        self.alternativePercent = None          self.m.cache[int(id)] = self @@ -961,6 +964,7 @@ class PyFile():      def getPercent(self):          """ get % of download """ +        if self.alternativePercent: return self.alternativePercent          try:              return int((float(self.plugin.req.dl_arrived)  / self.plugin.req.dl_size) * 100)          except: @@ -1021,7 +1025,7 @@ class PyPackage():      def getChildren(self):          """get information about contained links""" -        raise NotImplementedError +        return self.m.getPackageData(self.id)["links"]      def setPriority(self, priority):          self.priority = priority diff --git a/module/HookManager.py b/module/HookManager.py index 41e4c5ef0..3df8a1fc7 100644 --- a/module/HookManager.py +++ b/module/HookManager.py @@ -106,7 +106,7 @@ class HookManager():          for plugin in self.plugins:              if plugin.isActivated():                  if "packageFinished" in plugin.__threaded__: -                    self.startThread(plugin.packageFinished, pyfile) +                    self.startThread(plugin.packageFinished, package)                  else:                      plugin.packageFinished(package) diff --git a/module/PluginThread.py b/module/PluginThread.py index f6707a908..c666e7875 100644 --- a/module/PluginThread.py +++ b/module/PluginThread.py @@ -28,6 +28,7 @@ from module.plugins.Plugin import Fail  from module.plugins.Plugin import Reconnect  from module.plugins.Plugin import Retry  from pycurl import error +from module.FileDatabase import PyFile  ########################################################################  class PluginThread(Thread): @@ -255,8 +256,9 @@ class HookThread(PluginThread):          self.active = pyfile          m.localThreads.append(self) - -        pyfile.setStatus("processing") +         +        if isinstance(pyfile, PyFile): +            pyfile.setStatus("processing")          self.start() @@ -265,8 +267,8 @@ class HookThread(PluginThread):          self.m.localThreads.remove(self) -        self.active.finishIfDone() - +        if isinstance(self.active, PyFile): +            self.active.finishIfDone()  ########################################################################  class InfoThread(PluginThread): @@ -303,4 +305,4 @@ class InfoThread(PluginThread):                  self.m.core.log.debug("Finished Info Fetching for %s" % pluginname) -                self.m.core.files.save()
\ No newline at end of file +                self.m.core.files.save() diff --git a/module/plugins/Account.py b/module/plugins/Account.py index bdbbd4c1c..af8b6ebe8 100644 --- a/module/plugins/Account.py +++ b/module/plugins/Account.py @@ -40,7 +40,7 @@ class Account():      def setAccounts(self, accounts):          self.accounts = accounts -        for user, data in self.accounts: +        for user, data in self.accounts.iteritems():              self.login(user, data)      def updateAccounts(self, user, password, options): diff --git a/module/plugins/hooks/UnRar.py b/module/plugins/hooks/UnRar.py new file mode 100644 index 000000000..82c99a575 --- /dev/null +++ b/module/plugins/hooks/UnRar.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- + +""" +    This program is free software; you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation; either version 3 of the License, +    or (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +    See the GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program; if not, see <http://www.gnu.org/licenses/>. +     +    @author: mkaay +""" + +from __future__ import with_statement + +from module.plugins.Hook import Hook +from module.pyunrar import Unrar, WrongPasswordError, CommandError + +from os.path import exists, join +from os import remove +import re + +class UnRar(Hook): +    __name__ = "UnRar" +    __version__ = "0.1" +    __description__ = """unrar""" +    __config__ = [ ("activated", "bool", "Activated", True), +                   ("fullpath", "bool", "extract full path", True), +                   ("overwrite", "bool", "overwrite files", True), +                   ("passwordfile", "str", "unrar passoword file", "unrar_passwords.txt"), +                   ("deletearchive", "bool", "delete archives when done", False) ] +    __threaded__ = ["packageFinished"] +    __author_name__ = ("mkaay") +    __author_mail__ = ("mkaay@mkaay.de") +     +    def setup(self): +        self.comments = ["# one password each line"] +        self.passwords = [] +        if exists(self.getConfig("passwordfile")): +            with open(self.getConfig("passwordfile"), "r") as f: +                for l in f.readlines(): +                    l = l.strip("\n\r") +                    if l and not l.startswith("#"): +                        self.passwords.append(l) +        else: +            with open(self.getConfig("passwordfile"), "w") as f: +                f.writelines(self.comments) +        self.re_splitfile = re.compile("(.*)\.part(\d+)\.rar$") +     +    def addPassword(self, pw): +        if not pw in self.passwords: +            self.passwords.insert(0, pw) +            with open(self.getConfig("passwordfile"), "w") as f: +                f.writelines(self.comments) +                f.writelines(self.passwords) +         +    def removeFiles(self, pack, fname): +        if not self.getConfig("deletearchive"): +            return +        m = self.re_splitfile.search(fname) +                 +        download_folder = self.core.config['general']['download_folder'] +        if self.core.config['general']['folder_per_package']: +            folder = join(download_folder, pack.folder.decode(sys.getfilesystemencoding())) +        else: +            folder = download_folder +        if m: +            nre = re.compile("%s\.part\d+\.rar" % m.group(1)) +            for fid, data in pack.getChildren().iteritems(): +                if nre.match(data["name"]): +                    remove(join(folder, data["name"])) +        elif not m and fname.endswith(".rar"): +            nre = re.compile("^%s\.r..$" % fname.replace(".rar","")) +            for fid, data in pack.getChildren().iteritems(): +                if nre.match(data["name"]): +                    remove(join(folder, data["name"])) +     +    def packageFinished(self, pack): +        if pack.password: +            self.addPassword(pack.password) +        files = [] +        for fid, data in pack.getChildren().iteritems(): +            m = self.re_splitfile.search(data["name"]) +            if m and int(m.group(2)) == 1: +                files.append((fid,m.group(0))) +            elif not m and data["name"].endswith(".rar"): +                files.append((fid,data["name"])) +         +        for fid, fname in files: +            pyfile = self.core.files.getFile(fid) +            pyfile.setStatus("custom") +            def s(p): +                pyfile.alternativePercent = p +                 +            download_folder = self.core.config['general']['download_folder'] +            if self.core.config['general']['folder_per_package']: +                folder = join(download_folder, pack.folder.decode(sys.getfilesystemencoding())) +            else: +                folder = download_folder +             +            u = Unrar(join(folder, fname)) +            try: +                u.crackPassword(passwords=self.passwords, statusFunction=s, overwrite=True, destination=folder) +            except WrongPasswordError: +                continue +            except CommandError as e: +                if re.search("Cannot find volume", e.stderr): +                    continue +                try: +                    if e.getExitCode() == 1 and len(u.listContent(u.getPassword())) == 1: +                        self.removeFiles(pack, fname) +                except: +                    continue +            else: +                self.removeFiles(pack, fname) +            finally: +                pyfile.alternativePercent = None + diff --git a/module/pyunrar.py b/module/pyunrar.py new file mode 100644 index 000000000..4db3c3a19 --- /dev/null +++ b/module/pyunrar.py @@ -0,0 +1,370 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +    This program is free software; you can redistribute it and/or modify +    it under the terms of the GNU General Public License as published by +    the Free Software Foundation; either version 3 of the License, +    or (at your option) any later version. + +    This program is distributed in the hope that it will be useful, +    but WITHOUT ANY WARRANTY; without even the implied warranty of +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +    See the GNU General Public License for more details. + +    You should have received a copy of the GNU General Public License +    along with this program; if not, see <http://www.gnu.org/licenses/>. +     +    @author: mkaay <mkaay@mkaay.de> +""" + +from subprocess import Popen, PIPE +import re +from time import sleep +from tempfile import mkdtemp +from shutil import rmtree, move +from shutil import Error as FileError +from os.path import join, abspath, basename, dirname +from os import remove, makedirs + +EXITMAP = { +    255: ("USER BREAK 	User stopped the process"), +    9: ("CREATE ERROR", "Create file error"), +    8: ("MEMORY ERROR", "Not enough memory for operation"), +    7: ("USER ERROR", "Command line option error"), +    6: ("OPEN ERROR", "Open file error"), +    5: ("WRITE ERROR", "Write to disk error"), +    4: ("LOCKED ARCHIVE", "Attempt to modify an archive previously locked by the 'k' command"), +    3: ("CRC ERROR", "A CRC error occurred when unpacking"), +    2: ("FATAL ERROR", "A fatal error occurred"), +    1: ("WARNING", "Non fatal error(s) occurred"), +    0: ("SUCCESS", "Successful operation (User exit)"), +} + +class UnknownError(Exception): +    pass + +class NoFilesError(Exception): +    pass + +class WrongPasswordError(Exception): +    pass + +class CommandError(Exception): +    def __init__(self, ret=None, stdout=None, stderr=None): +        self.ret = ret +        self.stdout = stdout +        self.stderr = stderr +     +    def __str__(self): +        return "%s %s %s" % (EXITMAP[self.ret], self.stdout, self.stderr) +     +    def __repr__(self): +        try: +            return "<CommandError %s (%s)>" % (EXITMAP[self.ret][0], EXITMAP[self.ret][1]) +        except: +            return "<CommandError>" +     +    def getExitCode(self): +        return self.ret +     +    def getMappedExitCode(self): +        return EXITMAP[self.ret] + +class Unrar(): +    def __init__(self, archive): +        """ +            archive should be be first or only part +        """ +        self.archive = archive +        self.pattern = None +        m = re.match("^(.*).part(\d+).rar$", archive) +        if m: +            self.pattern = "%s.part*.rar" % m.group(1) +        else: #old style +            self.pattern = "%s.r*" % archive.replace(".rar", "") +        self.cmd = "unrar" +        self.encrypted = None +        self.headerEncrypted = None +        self.smallestFiles = None +        self.password = None +     +    def listContent(self, password=None): +        """ +            returns a list with all infos to the files in the archive +            dict keys: name, version, method, crc, attributes, time, date, ratio, size_packed, size +            @return list(dict, dict, ...) +        """ +        f = self.archive +        if self.pattern: +            f = self.pattern +        args = [self.cmd, "v"] +        if password: +            args.append("-p%s" % password) +        else: +            args.append("-p-") +        args.append(f) +        p = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) +        ret = p.wait() +        if ret == 3: +            self.headerEncrypted = True +            raise WrongPasswordError() +        elif ret == 0 and password: +            self.headerEncrypted = False +        o = p.stdout.read() +        inList = False +        infos = {} +        nameLine = False +        name = "" +        for line in o.split("\n"): +            if line == "-"*79: +                inList = not inList +                continue +            if inList: +                nameLine = not nameLine +                if nameLine: +                    name = line +                    if name[0] == "*": #check for pw indicator +                        name = name[1:] +                        self.encrypted = True +                    name = name.strip() +                    continue +                s = line.split(" ") +                s = [e for e in s if e] +                s.reverse() +                d = {} +                for k, v in zip(["version", "method", "crc", "attributes", "time", "date", "ratio", "size_packed", "size"], s[0:9]): +                    d[k] = v +                #if d["crc"] == "00000000" and len(d["method"]) == 2: +                if re.search("d", d["attributes"].lower()): #directory +                    continue +                d["name"] = name +                d["size_packed"] = int(d["size_packed"]) +                d["size"] = int(d["size"]) +                if infos.has_key(name): +                    infos[name]["size_packed"] = infos[name]["size_packed"] + d["size_packed"] +                    infos[name]["crc"].append(d["crc"]) +                else: +                    infos[name] = d +                    infos[name]["crc"] = [d["crc"]] +        infos = infos.values() +        return infos +     +    def listSimple(self, password=None): +        """ +            return a list with full path to all files +            @return list +        """ +        l = self.listContent(password=password) +        return [e["name"] for e in l] +     +    def getSmallestFile(self, password=None): +        """ +            return the file info for the smallest file +            @return dict +        """ +        files = self.listContent(password=password) +        smallest = (-1, -1) +        for i, f in enumerate(files): +            if f["size"] < smallest[1] or smallest[1] == -1: +                smallest = (i, f["size"]) +        if smallest[0] == -1: +            raise UnknownError() +        self.smallestFiles = files[smallest[0]] +        return files[smallest[0]] +     +    def needPassword(self): +        """ +            do we need a password? +            @return bool +        """ +        if not self.smallestFiles: +            try: +                self.getSmallestFile() +            except WrongPasswordError: +                return True +        return self.headerEncrypted or self.encrypted +     +    def checkPassword(self, password, statusFunction=None): +        """ +            check if password is okay +            @return bool +        """ +        if not self.needPassword(): +            return True +        f = self.archive +        if self.pattern: +            f = self.pattern +        args = [self.cmd, "t", "-p%s" % password, f] +        try: +            args.append(self.getSmallestFile(password)["name"]) +        except WrongPasswordError: +            return False +        p = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) +        (ret, out) = self.processOutput(p, statusFunction) +        if ret == 3: +            raise False +        elif ret == 0: +            return True +        else: +            raise UnknownError() +     +    def extract(self, password=None, fullPath=True, files=[], exclude=[], destination=None, overwrite=False, statusFunction=None): +        """ +            extract the archive +            @return bool: extract okay? +            raises WrongPasswordError or CommandError +        """ +        f = self.archive +        if self.pattern: +            f = self.pattern +        args = [self.cmd] +        if fullPath: +            args.append("x") +        else: +            args.append("e") +        if not password: +            password = "-" +        if overwrite: +            args.append("-o+") +        else: +            args.append("-o-") +        args.append("-p%s" % password) +        args.append(f) +        if files: +            args.extend([e for e in files]) +        if exclude: +            args.extend(["-x%s" % e for e in exclude]) +        if destination: +            args.append(destination) +        p = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) +        (ret, out) = self.processOutput(p, statusFunction) +        if ret == 3: +            raise WrongPasswordError() +        elif ret == 0: +            return True +        else: +            raise CommandError(ret=ret, stdout=out, stderr=p.stderr.read()) +     +    def crackPassword(self, passwords=[], fullPath=True, destination=None, overwrite=False, statusFunction=None, exclude=[]): +        """ +            check password list until the right one is found and extract the archive +            @return bool: password found? +        """ +        correctPassword = None +        if self.needPassword(): +            for password in passwords: +                sf = [] +                try: +                    sf.append(self.getSmallestFile(password)["name"]) +                except WrongPasswordError: +                    continue +                tdir = mkdtemp(prefix="rar") +                try: +                    self.extract(password=password, fullPath=fullPath, destination=tdir, overwrite=overwrite, statusFunction=statusFunction, files=sf) +                except WrongPasswordError: +                    continue +                else: +                    if not destination: +                        destination = "." +                    if overwrite: +                        try: +                            remove(abspath(join(destination, sf[0]))) +                        except OSError as e: +                            if not e.errno == 2: +                                raise e +                    f = sf[0] +                    d = destination +                    if fullPath: +                        try: +                            makedirs(dirname(join(abspath(destination), sf[0]))) +                        except OSError as e: +                            if not e.errno == 17: +                                raise e +                        d = join(destination, dirname(f)) +                    else: +                        f = basename(f) +                    try: +                        move(join(tdir, f), abspath(d)) +                    except FileError: +                        pass +                    exclude.append(sf[0]) +                    correctPassword = password +                    break +                finally: +                    rmtree(tdir) +                    pass +        try: +            self.extract(password=correctPassword, fullPath=fullPath, destination=destination, overwrite=overwrite, statusFunction=statusFunction, exclude=exclude) +            self.password = correctPassword +            return True +        except WrongPasswordError: +            return False +     +    def processOutput(self, p, statusFunction=None): +        """ +            internal method +            parse the progress output of the rar/unrar command +            @return int: exitcode +                    string: command output +        """ +        ret = None +        out = "" +        tmp = None +        count = 0 +        perc = 0 +        tperc = "0" +        last = None +        digits = "1 2 3 4 5 6 7 8 9 0".split(" ") +        if not statusFunction: +            statusFunction = lambda p: None +        statusFunction(0) +        while ret == None or tmp: +            tmp = p.stdout.read(1) +            if tmp: +                out += tmp +                if tmp == chr(8): +                    if last == tmp: +                        count += 1 +                        tperc = "0" +                    else: +                        count = 0 +                        if perc < int(tperc): +                            perc = int(tperc) +                            statusFunction(perc) +                elif count >= 3: +                    if tmp == "\n": +                        count = 0 +                    elif tmp in digits: +                        tperc += tmp +                last = tmp +            else: +                sleep(0.01) +            ret = p.poll() +        statusFunction(100) +        return ret, out +     +    def getPassword(self): +        """ +            return the correct password +            works only in conjunction with 'crackPassword' +            @return string: password +        """ +        return self.password + +if __name__ == "__main__": +    from pprint import pprint +    u = Unrar("archive.part1.rar", multi=True) +    u = Unrar("parchive.part1.rar", multi=True) +    pprint(u.listContent()) +    u = Unrar("pharchive.part1.rar", multi=True) +    pprint(u.listContent(password="test")) +    u = Unrar("bigarchive.rar") +    pprint(u.listContent()) +    print u.getSmallestFile() +    try: +        def s(p): +            print p +        print u.crackPassword(passwords=["test1", "ggfd", "423r", "test"], destination=".", statusFunction=s, overwrite=True) +    except CommandError as e: +        print e | 
