diff options
Diffstat (limited to 'pyload/plugins/Crypter.py')
| -rw-r--r-- | pyload/plugins/Crypter.py | 286 | 
1 files changed, 286 insertions, 0 deletions
| diff --git a/pyload/plugins/Crypter.py b/pyload/plugins/Crypter.py new file mode 100644 index 000000000..af3d5aba7 --- /dev/null +++ b/pyload/plugins/Crypter.py @@ -0,0 +1,286 @@ +# -*- coding: utf-8 -*- + +from pyload.Api import LinkStatus, DownloadStatus as DS +from pyload.utils import to_list, has_method, uniqify +from pyload.utils.fs import exists, remove, fs_encode +from Base import Base, Retry + + +class Package: +    """ Container that indicates that a new package should be created """ + +    def __init__(self, name=None, links=None): +        self.name = name +        self.links = [] + +        if links: +            self.addLinks(links) + +        # nested packages +        self.packs = [] + +    def addLinks(self, links): +        """ Add one or multiple links to the package + +        :param links: One or list of urls or :class:`LinkStatus` +        """ +        links = to_list(links) +        for link in links: +            if not isinstance(link, LinkStatus): +                link = LinkStatus(link, link, -1, DS.Queued) + +            self.links.append(link) + +    def addPackage(self, pack): +        self.packs.append(pack) + +    def getURLs(self): +        return [link.url for link in self.links] + +    def getAllURLs(self): +        urls = self.getURLs() +        for p in self.packs: +            urls.extend(p.getAllURLs()) +        return urls + +    # same name and urls is enough to be equal for packages +    def __eq__(self, other): +        return self.name == other.name and self.links == other.links + +    def __repr__(self): +        return u"<CrypterPackage name=%s, links=[%s], packs=%s" % (self.name, ",".join(str(l) for l in self.links), +                                                                   self.packs) + +    def __hash__(self): +        return hash(self.name) ^ hash(frozenset(self.links)) ^ hash(self.name) + + +class PyFileMockup: +    """ Legacy class needed by old crypter plugins """ + +    def __init__(self, url, pack): +        self.url = url +        self.name = url +        self._package = None +        self.packageid = pack.id if pack else -1 + +    def package(self): +        return self._package + + +class Crypter(Base): +    """ +    Base class for (de)crypter plugins. Overwrite decrypt* methods. + +    How to use decrypt* methods: + +    You have to overwrite at least one method of decryptURL, decryptURLs, decryptFile. + +    After decrypting and generating urls/packages you have to return the result. +    Valid return Data is: + +    :class:`Package` instance Crypter.Package +        A **new** package will be created with the name and the urls of the object. + +    List of urls and `Package` instances +        All urls in the list will be added to the **current** package. For each `Package`\ +        instance a new package will be created. + +    """ + +    #: Prefix to annotate that the submited string for decrypting is indeed file content +    CONTENT_PREFIX = "filecontent:" + +    #: Optional name of an account plugin that should be used, but does not guarantee that one is available +    USE_ACCOUNT = None + +    #: When True this crypter will not be decrypted directly and queued one by one. +    # Needed for crypter that can't run in parallel or need to wait between decryption. +    QUEUE_DECRYPT = False + +    @classmethod +    def decrypt(cls, core, url_or_urls, password=None): +        """Static method to decrypt urls or content. Can be used by other plugins. +        To decrypt file content prefix the string with ``CONTENT_PREFIX `` as seen above. + +        :param core: pyLoad `Core`, needed in decrypt context +        :param url_or_urls: List of urls or single url/ file content +        :param password: optional password used for decrypting + +        :raises Exception: No decryption errors are cascaded +        :return: List of decrypted urls, all package info removed +        """ +        urls = to_list(url_or_urls) +        p = cls(core, password) +        try: +            result = p._decrypt(urls) +        finally: +            p.clean() + +        ret = [] + +        for url_or_pack in result: +            if isinstance(url_or_pack, Package): #package +                ret.extend(url_or_pack.getAllURLs()) +            else: # single url +                ret.append(url_or_pack) +                # eliminate duplicates +        return uniqify(ret) + +    # TODO: pass user to crypter +    # TODO: crypter could not only know url, but also the name and size +    def __init__(self, core, password=None): +        Base.__init__(self, core) + +        self.req = None +        # load account if set +        if self.USE_ACCOUNT: +            self.account = self.core.accountManager.selectAccount(self.USE_ACCOUNT, self.user) +            if self.account: +                self.req = self.account.getAccountRequest() + +        if self.req is None: +            self.req = core.requestFactory.getRequest() + +        #: Password supplied by user +        self.password = password + +        # For old style decrypter, do not use these! +        self.packages = [] +        self.urls = [] +        self.pyfile = None + +        self.init() + +    def init(self): +        """More init stuff if needed""" + +    def setup(self): +        """Called everytime before decrypting. A Crypter plugin will be most likely used for several jobs.""" + +    def decryptURL(self, url): +        """Decrypt a single url + +        :param url: url to decrypt +        :return: See :class:`Crypter` Documentation +        """ +        if url.startswith("http"): # basic method to redirect +            return self.decryptFile(self.load(url)) +        else: +            self.fail(_("Not existing file or unsupported protocol")) + +    def decryptURLs(self, urls): +        """Decrypt a bunch of urls + +        :param urls: list of urls +        :return: See :class:`Crypter` Documentation +        """ +        raise NotImplementedError + +    def decryptFile(self, content): +        """Decrypt file content + +        :param content: content to decrypt as string +        :return: See :class:`Crypter` Documentation +        """ +        raise NotImplementedError + +    def _decrypt(self, urls): +        """Internal method to select decrypting method + +        :param urls: List of urls/content +        :return: +        """ +        cls = self.__class__ + +        # separate local and remote files +        content, urls = self.getLocalContent(urls) +        result = [] + +        if urls and has_method(cls, "decrypt"): +            self.logDebug("Deprecated .decrypt() method in Crypter plugin") +            result = [] +            for url in urls: +                self.pyfile = PyFileMockup(url) +                self.setup() +                self.decrypt(self.pyfile) +                result.extend(self.convertPackages()) +        elif urls: +            method = True +            try: +                self.setup() +                result = to_list(self.decryptURLs(urls)) +            except NotImplementedError: +                method = False + +            # this will raise error if not implemented +            if not method: +                for url in urls: +                    self.setup() +                    result.extend(to_list(self.decryptURL(url))) + +        for f, c in content: +            self.setup() +            result.extend(to_list(self.decryptFile(c))) +            try: +                if f.startswith("tmp_"): remove(f) +            except IOError: +                self.logWarning(_("Could not remove file '%s'") % f) +                self.core.print_exc() + +        return result + +    def getLocalContent(self, urls): +        """Load files from disk and separate to file content and url list + +        :param urls: +        :return: list of (filename, content), remote urls +        """ +        content = [] +        # do nothing if no decryptFile method +        if hasattr(self.__class__, "decryptFile"): +            remote = [] +            for url in urls: +                path = None +                if url.startswith("http"): # skip urls directly +                    pass +                elif url.startswith(self.CONTENT_PREFIX): +                    path = url +                elif exists(url): +                    path = url +                elif exists(self.core.path(url)): +                    path = self.core.path(url) + +                if path: +                    try: +                        if path.startswith(self.CONTENT_PREFIX): +                            content.append(("", path[len(self.CONTENT_PREFIX)])) +                        else: +                            f = open(fs_encode(path), "rb") +                            content.append((f.name, f.read())) +                            f.close() +                    except IOError, e: +                        self.logError("IOError", e) +                else: +                    remote.append(url) + +            #swap filtered url list +            urls = remote + +        return content, urls + +    def retry(self): +        """ Retry decrypting, will only work once. Somewhat deprecated method, should be avoided. """ +        raise Retry() + +    def convertPackages(self): +        """ Deprecated """ +        self.logDebug("Deprecated method .convertPackages()") +        res = [Package(name, urls) for name, urls in self.packages] +        res.extend(self.urls) +        return res + +    def clean(self): +        if hasattr(self, "req"): +            self.req.close() +            del self.req
\ No newline at end of file | 
