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 |