diff options
Diffstat (limited to 'pyload')
-rw-r--r-- | pyload/Core.py | 4 | ||||
-rw-r--r-- | pyload/api/DownloadPreparingApi.py | 18 | ||||
-rw-r--r-- | pyload/datatypes/OnlineCheck.py | 33 | ||||
-rw-r--r-- | pyload/datatypes/PyFile.py | 6 | ||||
-rw-r--r-- | pyload/plugins/Base.py | 30 | ||||
-rw-r--r-- | pyload/plugins/Crypter.py | 77 | ||||
-rw-r--r-- | pyload/plugins/Hoster.py | 6 | ||||
-rw-r--r-- | pyload/plugins/network/CurlRequest.py | 1 | ||||
-rw-r--r-- | pyload/remote/apitypes.py | 7 | ||||
-rw-r--r-- | pyload/remote/apitypes_debug.py | 2 | ||||
-rw-r--r-- | pyload/remote/pyload.thrift | 7 | ||||
-rw-r--r-- | pyload/remote/wsbackend/AsyncHandler.py | 2 | ||||
-rw-r--r-- | pyload/threads/BaseThread.py | 41 | ||||
-rw-r--r-- | pyload/threads/DecrypterThread.py | 61 | ||||
-rw-r--r-- | pyload/threads/InfoThread.py | 199 | ||||
-rw-r--r-- | pyload/threads/ThreadManager.py | 46 | ||||
-rw-r--r-- | pyload/utils/__init__.py | 2 | ||||
-rw-r--r-- | pyload/web/app/scripts/helpers/linkStatus.js | 2 | ||||
-rw-r--r-- | pyload/web/app/scripts/models/LinkStatus.js | 5 | ||||
-rw-r--r-- | pyload/web/app/styles/default/linkgrabber.less | 5 | ||||
-rw-r--r-- | pyload/web/app/templates/default/linkgrabber/package.html | 4 |
21 files changed, 286 insertions, 272 deletions
diff --git a/pyload/Core.py b/pyload/Core.py index 2d591a2bb..f97cfcf3b 100644 --- a/pyload/Core.py +++ b/pyload/Core.py @@ -599,8 +599,8 @@ class Core(object): if not self.pdb: self.pdb = Pdb() self.pdb.set_trace() - def print_exc(self): - if self.debug: + def print_exc(self, force=False): + if self.debug or force: print_exc() def path(self, *args): diff --git a/pyload/api/DownloadPreparingApi.py b/pyload/api/DownloadPreparingApi.py index 68c83e97d..131f73b1d 100644 --- a/pyload/api/DownloadPreparingApi.py +++ b/pyload/api/DownloadPreparingApi.py @@ -37,16 +37,16 @@ class DownloadPreparingApi(ApiComponent): def checkLinks(self, links): """ initiates online status check, will also decrypt files. - :param urls: + :param links: :return: initial set of data as :class:`OnlineCheck` instance containing the result id """ hoster, crypter = self.core.pluginManager.parseUrls(links) #: TODO: withhold crypter, derypt or add later # initial result does not contain the crypter links - tmp = [(url, LinkStatus(url, url, pluginname, -1, DS.Queued)) for url, pluginname in hoster + crypter] + tmp = [(url, LinkStatus(url, url, -1, DS.Queued, pluginname)) for url, pluginname in hoster + crypter] data = parseNames(tmp) - rid = self.core.threadManager.createResultThread(data) + rid = self.core.threadManager.createResultThread(self.primaryUID, data) return OnlineCheck(rid, data) @@ -54,8 +54,7 @@ class DownloadPreparingApi(ApiComponent): def checkContainer(self, filename, data): """ checks online status of urls and a submitted container file - :param urls: list of urls - :param container: container file name + :param filename: name of the file :param data: file content :return: :class:`OnlineCheck` """ @@ -88,13 +87,8 @@ class DownloadPreparingApi(ApiComponent): :return: `OnlineCheck`, if rid is -1 then there is no more data available """ result = self.core.threadManager.getInfoResult(rid) - - if "ALL_INFO_FETCHED" in result: - del result["ALL_INFO_FETCHED"] - return OnlineCheck(-1, result) - else: - return OnlineCheck(rid, result) - + if result and result.owner == self.primaryUID: + return result.toApiData() @RequirePerm(Permission.Add) def generatePackages(self, links): diff --git a/pyload/datatypes/OnlineCheck.py b/pyload/datatypes/OnlineCheck.py index 4b31e848b..2797828bf 100644 --- a/pyload/datatypes/OnlineCheck.py +++ b/pyload/datatypes/OnlineCheck.py @@ -1 +1,32 @@ -__author__ = 'christian' +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from time import time + +from pyload.Api import OnlineCheck as OC + +class OnlineCheck: + """ Helper class that holds result of an initiated online check """ + + def __init__(self, rid, owner): + self.rid = rid + self.owner = owner + self.result = {} + self.done = False + + self.timestamp = time() + + def update(self, result): + self.timestamp = time() + self.result.update(result) + + def toApiData(self): + self.timestamp = time() + oc = OC(self.rid, self.result) + # getting the results clears the older ones + self.result = {} + # indication for no more data + if self.done: + oc.rid = -1 + + return oc
\ No newline at end of file diff --git a/pyload/datatypes/PyFile.py b/pyload/datatypes/PyFile.py index 2cc28fa24..de7288d22 100644 --- a/pyload/datatypes/PyFile.py +++ b/pyload/datatypes/PyFile.py @@ -49,7 +49,7 @@ class PyFile(object): Represents a file object at runtime """ __slots__ = ("m", "fid", "_name", "_size", "filestatus", "media", "added", "fileorder", - "url", "pluginname", "hash", "status", "error", "packageid", "ownerid", + "url", "pluginname", "hash", "status", "error", "packageid", "owner", "lock", "plugin", "waitUntil", "abort", "statusname", "reconnected", "pluginclass") @@ -83,7 +83,7 @@ class PyFile(object): self.hash = hash self.status = status self.error = error - self.ownerid = owner + self.owner = owner self.packageid = package # database information ends here @@ -183,7 +183,7 @@ class PyFile(object): def toInfoData(self): - return FileInfo(self.fid, self.getName(), self.packageid, self.ownerid, self.getSize(), self.filestatus, + return FileInfo(self.fid, self.getName(), self.packageid, self.owner, self.getSize(), self.filestatus, self.media, self.added, self.fileorder, DownloadInfo( self.url, self.pluginname, self.hash, self.status, self.getStatusName(), self.error ) diff --git a/pyload/plugins/Base.py b/pyload/plugins/Base.py index 3ca8abdd0..abb59a7bc 100644 --- a/pyload/plugins/Base.py +++ b/pyload/plugins/Base.py @@ -1,21 +1,19 @@ # -*- 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: RaNaN -""" +############################################################################### +# Copyright(c) 2008-2013 pyLoad Team +# http://www.pyload.org +# +# This file is part of pyLoad. +# pyLoad is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# Subjected to the terms and conditions in LICENSE +# +# @author: RaNaN +############################################################################### import sys from time import time, sleep diff --git a/pyload/plugins/Crypter.py b/pyload/plugins/Crypter.py index cce43fb56..2175b5f94 100644 --- a/pyload/plugins/Crypter.py +++ b/pyload/plugins/Crypter.py @@ -1,50 +1,60 @@ # -*- coding: utf-8 -*- -from traceback import print_exc - -from pyload.Api import LinkStatus +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 pyload.utils.packagetools import parseNames -from Base import Base, Retry +from Base import Base, Fail, Retry, Abort class Package: """ Container that indicates that a new package should be created """ - def __init__(self, name=None, urls=None): + def __init__(self, name=None, links=None): self.name = name - self.urls = urls if urls else [] + self.links = [] + + if links: + self.addLinks(links) # nested packages self.packs = [] - def addURL(self, url): - self.urls.append(url) + 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) - def addLink(self, url, name, status, size): - # TODO: allow to add urls with known information - pass + 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.urls + 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.urls == other.urls + return self.name == other.name and self.links == other.links def __repr__(self): - return u"<CrypterPackage name=%s, links=%s, packs=%s" % (self.name, self.urls, self.packs) + 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.urls)) ^ hash(self.name) + return hash(self.name) ^ hash(frozenset(self.links)) ^ hash(self.name) class PyFileMockup: @@ -53,7 +63,7 @@ class PyFileMockup: def __init__(self, url, pack): self.url = url self.name = url - self._package = pack + self._package = None self.packageid = pack.id if pack else -1 def package(self): @@ -91,18 +101,21 @@ class Crypter(Base): QUEUE_DECRYPT = False @classmethod - def decrypt(cls, core, url_or_urls): + 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) + p = cls(core, password) try: - result = p.processDecrypt(urls) + result = p._decrypt(urls) finally: p.clean() @@ -113,12 +126,12 @@ class Crypter(Base): ret.extend(url_or_pack.getAllURLs()) else: # single url ret.append(url_or_pack) - # eliminate duplicates + # 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, package=None, password=None): + def __init__(self, core, password=None): Base.__init__(self, core) self.req = None @@ -131,8 +144,6 @@ class Crypter(Base): if self.req is None: self.req = core.requestFactory.getRequest() - # Package the plugin was initialized for, don't use this, its not guaranteed to be set - self.package = package #: Password supplied by user self.password = password @@ -186,7 +197,7 @@ class Crypter(Base): return [Package(name, purls) for name, purls in parseNames([(url, url) for url in urls]).iteritems()] def _decrypt(self, urls): - """ Internal method to select decrypting method + """Internal method to select decrypting method :param urls: List of urls/content :return: @@ -208,7 +219,7 @@ class Crypter(Base): self.logDebug("Deprecated .decrypt() method in Crypter plugin") result = [] for url in urls: - self.pyfile = PyFileMockup(url, self.package) + self.pyfile = PyFileMockup(url) self.setup() self.decrypt(self.pyfile) result.extend(self.convertPackages()) @@ -223,22 +234,12 @@ class Crypter(Base): result.extend(to_list(self.decryptFile(c))) try: if f.startswith("tmp_"): remove(f) - except: - pass + except IOError: + self.logWarning(_("Could not remove file '%s'") % f) + self.core.print_exc() return result - def processDecrypt(self, urls): - """Catches all exceptions in decrypt methods and return results - - :return: Decrypting results - """ - try: - return to_list(self._decrypt(urls)) - except Exception: - self.core.print_exc() - return [] - def getLocalContent(self, urls): """Load files from disk and separate to file content and url list diff --git a/pyload/plugins/Hoster.py b/pyload/plugins/Hoster.py index bc1e4f9d0..4d96c5730 100644 --- a/pyload/plugins/Hoster.py +++ b/pyload/plugins/Hoster.py @@ -58,16 +58,14 @@ class Hoster(Base): @staticmethod def getInfo(urls): """This method is used to retrieve the online status of files for hoster plugins. - It has to *yield* list of tuples with the result in this format (name, size, status, url), - where status is one of API pyfile statuses. :param urls: List of urls - :return: yield list of tuple with results (name, size, status, url) + :return: yield list of :class:`LinkStatus` as result """ pass def __init__(self, pyfile): - Base.__init__(self, pyfile.m.core) + Base.__init__(self, pyfile.m.core, pyfile.owner) self.wantReconnect = False #: enables simultaneous processing of multiple downloads diff --git a/pyload/plugins/network/CurlRequest.py b/pyload/plugins/network/CurlRequest.py index 182553ed1..8d1f22450 100644 --- a/pyload/plugins/network/CurlRequest.py +++ b/pyload/plugins/network/CurlRequest.py @@ -40,6 +40,7 @@ def myurlencode(data): bad_headers = range(400, 418) + range(500, 506) +pycurl.global_init(pycurl.GLOBAL_DEFAULT) class CurlRequest(Request): """ Request class based on libcurl """ diff --git a/pyload/remote/apitypes.py b/pyload/remote/apitypes.py index d186717d4..53d2de6d2 100644 --- a/pyload/remote/apitypes.py +++ b/pyload/remote/apitypes.py @@ -241,14 +241,15 @@ class InvalidConfigSection(ExceptionObject): self.section = section class LinkStatus(BaseObject): - __slots__ = ['url', 'name', 'plugin', 'size', 'status'] + __slots__ = ['url', 'name', 'size', 'status', 'plugin', 'hash'] - def __init__(self, url=None, name=None, plugin=None, size=None, status=None): + def __init__(self, url=None, name=None, size=None, status=None, plugin=None, hash=None): self.url = url self.name = name - self.plugin = plugin self.size = size self.status = status + self.plugin = plugin + self.hash = hash class OnlineCheck(BaseObject): __slots__ = ['rid', 'data'] diff --git a/pyload/remote/apitypes_debug.py b/pyload/remote/apitypes_debug.py index 1c34b702e..74ea8a6a8 100644 --- a/pyload/remote/apitypes_debug.py +++ b/pyload/remote/apitypes_debug.py @@ -32,7 +32,7 @@ classes = { 'Input' : [int, (None, basestring), (None, basestring)], 'InteractionTask' : [int, int, Input, basestring, basestring, basestring], 'InvalidConfigSection' : [basestring], - 'LinkStatus' : [basestring, basestring, basestring, int, int], + 'LinkStatus' : [basestring, basestring, int, int, (None, basestring), (None, basestring)], 'OnlineCheck' : [int, (dict, basestring, LinkStatus)], 'PackageDoesNotExists' : [int], 'PackageInfo' : [int, basestring, basestring, int, int, basestring, basestring, basestring, int, (list, basestring), int, bool, int, PackageStats, (list, int), (list, int)], diff --git a/pyload/remote/pyload.thrift b/pyload/remote/pyload.thrift index d41b6d10b..905be22b0 100644 --- a/pyload/remote/pyload.thrift +++ b/pyload/remote/pyload.thrift @@ -197,9 +197,10 @@ struct TreeCollection { struct LinkStatus { 1: string url, 2: string name, - 3: PluginName plugin, - 4: ByteCount size, // size <= 0 : unknown - 5: DownloadStatus status, + 3: ByteCount size, // size <= 0 : unknown + 4: DownloadStatus status, + 5: optional PluginName plugin, + 6: optional string hash } struct ServerStatus { diff --git a/pyload/remote/wsbackend/AsyncHandler.py b/pyload/remote/wsbackend/AsyncHandler.py index 136cc060d..c7a26cd6b 100644 --- a/pyload/remote/wsbackend/AsyncHandler.py +++ b/pyload/remote/wsbackend/AsyncHandler.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- ############################################################################### -# Copyright(c) 2008-2012 pyLoad Team +# Copyright(c) 2008-2013 pyLoad Team # http://www.pyload.org # # This file is part of pyLoad. diff --git a/pyload/threads/BaseThread.py b/pyload/threads/BaseThread.py index deaf03461..b7912e924 100644 --- a/pyload/threads/BaseThread.py +++ b/pyload/threads/BaseThread.py @@ -1,9 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import sys from threading import Thread from time import strftime, gmtime -from sys import exc_info from types import MethodType from pprint import pformat from traceback import format_exc @@ -11,17 +11,18 @@ from traceback import format_exc from pyload.utils import primary_uid from pyload.utils.fs import listdir, join, save_join, stat, exists + class BaseThread(Thread): """abstract base class for thread types""" - def __init__(self, manager): + def __init__(self, manager, ower=None): Thread.__init__(self) self.setDaemon(True) self.m = manager #thread manager self.core = manager.core self.log = manager.core.log - #: Owner of the thread, every type should set it + #: Owner of the thread, every type should set it or overwrite user self.owner = None @property @@ -40,13 +41,13 @@ class BaseThread(Thread): dump_name = "debug_%s_%s.zip" % (name, strftime("%d-%m-%Y_%H-%M-%S")) if pyfile: - dump = self.getFileDump(pyfile) + dump = self.getPluginDump(pyfile.plugin) + "\n" + dump += self.getFileDump(pyfile) else: dump = self.getPluginDump(plugin) try: import zipfile - zip = zipfile.ZipFile(dump_name, "w") if exists(join("tmp", name)): @@ -81,11 +82,11 @@ class BaseThread(Thread): self.log.info("Debug Report written to %s" % dump_name) return dump_name - def getFileDump(self, pyfile): + def getPluginDump(self, plugin): dump = "pyLoad %s Debug Report of %s %s \n\nTRACEBACK:\n %s \n\nFRAMESTACK:\n" % ( - self.m.core.api.getServerVersion(), pyfile.pluginname, pyfile.plugin.__version__, format_exc()) + self.m.core.api.getServerVersion(), plugin.__name__, plugin.__version__, format_exc()) - tb = exc_info()[2] + tb = sys.exc_info()[2] stack = [] while tb: stack.append(tb.tb_frame) @@ -109,8 +110,8 @@ class BaseThread(Thread): dump += "\n\nPLUGIN OBJECT DUMP: \n\n" - for name in dir(pyfile.plugin): - attr = getattr(pyfile.plugin, name) + for name in dir(plugin): + attr = getattr(plugin, name) if not name.endswith("__") and type(attr) != MethodType: dump += "\t%20s = " % name try: @@ -118,7 +119,10 @@ class BaseThread(Thread): except Exception, e: dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n" - dump += "\nPYFILE OBJECT DUMP: \n\n" + return dump + + def getFileDump(self, pyfile): + dump = "PYFILE OBJECT DUMP: \n\n" for name in dir(pyfile): attr = getattr(pyfile, name) @@ -129,14 +133,13 @@ class BaseThread(Thread): except Exception, e: dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n" - dump += "\n\nCONFIG: \n\n" - dump += pformat(self.m.core.config.values) + "\n" - return dump - #TODO - def getPluginDump(self, plugin): - return "" - def getSystemDump(self): - return "" + dump = "SYSTEM:\n\n" + dump += """Platform: %s +Version: %s +Encoding: %s +FS-Encoding: %s + """ % (sys.platform, sys.version, sys.getdefaultencoding(), sys.getfilesystemencoding()) + return dump diff --git a/pyload/threads/DecrypterThread.py b/pyload/threads/DecrypterThread.py index e4df2ee75..e8b889ac8 100644 --- a/pyload/threads/DecrypterThread.py +++ b/pyload/threads/DecrypterThread.py @@ -2,10 +2,9 @@ # -*- coding: utf-8 -*- from time import sleep -from traceback import print_exc -from pyload.utils import uniqify -from pyload.plugins.Base import Retry +from pyload.utils import uniqify, accumulate +from pyload.plugins.Base import Abort, Retry from pyload.plugins.Crypter import Package from BaseThread import BaseThread @@ -14,30 +13,35 @@ class DecrypterThread(BaseThread): """thread for decrypting""" def __init__(self, manager, data, pid): - """constructor""" + # TODO: owner BaseThread.__init__(self, manager) + # [... (plugin, url) ...] self.data = data self.pid = pid self.start() def run(self): - plugin_map = {} - for url, plugin in self.data: - if plugin in plugin_map: - plugin_map[plugin].append(url) - else: - plugin_map[plugin] = [url] + pack = self.m.core.files.getPackage(self.pid) + links, packages = self.decrypt(accumulate(self.data), pack.password) - self.decrypt(plugin_map) + if links: + self.log.info(_("Decrypted %(count)d links into package %(name)s") % {"count": len(links), "name": pack.name}) + self.m.core.api.addFiles(self.pid, [l.url for l in links]) - def decrypt(self, plugin_map): - pack = self.m.core.files.getPackage(self.pid) + # TODO: add single package into this one and rename it? + # TODO: nested packages + for p in packages: + self.m.core.api.addPackage(p.name, p.getURLs(), pack.password) + + def decrypt(self, plugin_map, password=None): result = [] + # TODO QUEUE_DECRYPT + for name, urls in plugin_map.iteritems(): klass = self.m.core.pluginManager.loadClass("crypter", name) - plugin = klass(self.m.core, pack, pack.password) + plugin = klass(self.m.core, password) plugin_result = [] try: @@ -46,36 +50,37 @@ class DecrypterThread(BaseThread): except Retry: sleep(1) plugin_result = plugin._decrypt(urls) + except Abort: + plugin.logInfo(_("Decrypting aborted")) except Exception, e: plugin.logError(_("Decrypting failed"), e) - if self.m.core.debug: - print_exc() + if self.core.debug: + self.core.print_exc() self.writeDebugReport(plugin.__name__, plugin=plugin) + finally: + plugin.clean() plugin.logDebug("Decrypted", plugin_result) result.extend(plugin_result) - #TODO package names are optional - result = uniqify(result) + # generated packages pack_names = {} + # urls without package urls = [] + # merge urls and packages for p in result: if isinstance(p, Package): if p.name in pack_names: pack_names[p.name].urls.extend(p.urls) else: - pack_names[p.name] = p + if not p.name: + urls.append(p) + else: + pack_names[p.name] = p else: urls.append(p) - if urls: - self.log.info(_("Decrypted %(count)d links into package %(name)s") % {"count": len(urls), "name": pack.name}) - self.m.core.api.addFiles(self.pid, urls) - - for p in pack_names.itervalues(): - self.m.core.api.addPackage(p.name, p.urls, pack.password) - - if not result: - self.log.info(_("No links decrypted")) + urls = uniqify(urls) + return urls, pack_names.values()
\ No newline at end of file diff --git a/pyload/threads/InfoThread.py b/pyload/threads/InfoThread.py index fb4bdf11e..b62596ad3 100644 --- a/pyload/threads/InfoThread.py +++ b/pyload/threads/InfoThread.py @@ -9,25 +9,23 @@ from pyload.utils.packagetools import parseNames from pyload.utils import has_method, accumulate from BaseThread import BaseThread +from DecrypterThread import DecrypterThread -class InfoThread(BaseThread): - def __init__(self, manager, data, pid=-1, rid=-1): - """Constructor""" - BaseThread.__init__(self, manager) - self.data = data - self.pid = pid # package id - # [ .. (name, plugin) .. ] - - self.rid = rid #result id +class InfoThread(DecrypterThread): + def __init__(self, manager, owner, data, pid=-1, oc=None): + BaseThread.__init__(self, manager, owner) - self.cache = [] #accumulated data + # [... (plugin, url) ...] + self.data = data + self.pid = pid + self.oc = oc # online check + # urls that already have a package name + self.names = {} self.start() def run(self): - """run method""" - plugins = accumulate(self.data) crypter = {} @@ -37,93 +35,82 @@ class InfoThread(BaseThread): crypter[name] = plugins[name] del plugins[name] - #directly write to database - if self.pid > -1: - for pluginname, urls in plugins.iteritems(): - plugin = self.m.core.pluginManager.getPluginModule(pluginname) - klass = self.m.core.pluginManager.getPluginClass(pluginname) - if has_method(klass, "getInfo"): - self.fetchForPlugin(pluginname, klass, urls, self.updateDB) - self.m.core.files.save() - elif has_method(plugin, "getInfo"): - self.log.debug("Deprecated .getInfo() method on module level, use classmethod instead") - self.fetchForPlugin(pluginname, plugin, urls, self.updateDB) - self.m.core.files.save() - - else: #post the results - for name, urls in crypter.iteritems(): - #attach container content - try: - data = self.decrypt(name, urls) - except: - print_exc() - self.m.log.error("Could not decrypt content.") - data = [] - - accumulate(data, plugins) - - self.m.infoResults[self.rid] = {} - - for pluginname, urls in plugins.iteritems(): - plugin = self.m.core.pluginManager.getPluginModule(pluginname) - klass = self.m.core.pluginManager.getPluginClass(pluginname) - if has_method(klass, "getInfo"): - self.fetchForPlugin(pluginname, plugin, urls, self.updateResult, True) - #force to process cache - if self.cache: - self.updateResult(pluginname, [], True) - elif has_method(plugin, "getInfo"): - self.log.debug("Deprecated .getInfo() method on module level, use staticmethod instead") - self.fetchForPlugin(pluginname, plugin, urls, self.updateResult, True) - #force to process cache - if self.cache: - self.updateResult(pluginname, [], True) - else: - #generate default result - result = [(url, 0, 3, url) for url in urls] - - self.updateResult(pluginname, result, True) - - self.m.infoResults[self.rid]["ALL_INFO_FETCHED"] = {} - + if crypter: + # decrypt them + links, packages = self.decrypt(crypter) + # push these as initial result and save package names + self.updateResult(links) + for pack in packages: + for url in pack.getURLs(): + self.names[url] = pack.name + + links.extend(pack.links) + self.updateResult(pack.links) + + # TODO: no plugin information pushed to GUI + # parse links and merge + hoster, crypter = self.m.core.pluginManager.parseUrls([l.url for l in links]) + accumulate(hoster + crypter, plugins) + + # db or info result + cb = self.updateDB if self.pid > 1 else self.updateResult + + for pluginname, urls in plugins.iteritems(): + plugin = self.m.core.pluginManager.getPluginModule(pluginname) + klass = self.m.core.pluginManager.getPluginClass(pluginname) + if has_method(klass, "getInfo"): + self.fetchForPlugin(klass, urls, cb) + # TODO: this branch can be removed in the future + elif has_method(plugin, "getInfo"): + self.log.debug("Deprecated .getInfo() method on module level, use staticmethod instead") + self.fetchForPlugin(plugin, urls, cb) + + self.oc.done = True + self.names.clear() self.m.timestamp = time() + 5 * 60 - - def updateDB(self, plugin, result): - self.m.core.files.updateFileInfo(result, self.pid) - - def updateResult(self, plugin, result, force=False): - #parse package name and generate result - #accumulate results - - self.cache.extend(result) - - if len(self.cache) >= 20 or force: - #used for package generating - tmp = [(name, LinkStatus(url, name, plugin, int(size), status)) - for name, size, status, url in self.cache] - - data = parseNames(tmp) - self.m.setInfoResults(self.rid, data) - - self.cache = [] - - def updateCache(self, plugin, result): - self.cache.extend(result) - - def fetchForPlugin(self, pluginname, plugin, urls, cb, err=None): + def updateDB(self, result): + # writes results to db + # convert link info to tuples + info = [(l.name, l.size, l.status, l.url) for l in result if not l.hash] + info_hash = [(l.name, l.size, l.status, l.hash, l.url) for l in result if l.hash] + if info: + self.m.core.files.updateFileInfo(info, self.pid) + if info_hash: + self.m.core.files.updateFileInfo(info_hash, self.pid) + + def updateResult(self, result): + tmp = {} + parse = [] + # separate these with name and without + for link in result: + if link.url in self.names: + tmp[link] = self.names[link.url] + else: + parse.append(link) + + data = parseNames([(link.name, link) for link in parse]) + # merge in packages that already have a name + data = accumulate(tmp.iteritems(), data) + + self.m.setInfoResults(self.oc, data) + + def fetchForPlugin(self, plugin, urls, cb): + """executes info fetching for given plugin and urls""" + # also works on module names + pluginname = plugin.__name__.split(".")[-1] try: - result = [] #result loaded from cache + cached = [] #results loaded from cache process = [] #urls to process for url in urls: if url in self.m.infoCache: - result.append(self.m.infoCache[url]) + cached.append(self.m.infoCache[url]) else: process.append(url) - if result: - self.m.log.debug("Fetched %d values from cache for %s" % (len(result), pluginname)) - cb(pluginname, result) + if cached: + self.m.log.debug("Fetched %d links from cache for %s" % (len(cached), pluginname)) + cb(cached) if process: self.m.log.debug("Run Info Fetching for %s" % pluginname) @@ -131,26 +118,26 @@ class InfoThread(BaseThread): #result = [ .. (name, size, status, url) .. ] if not type(result) == list: result = [result] + links = [] + # Convert results to link statuses for res in result: - self.m.infoCache[res[3]] = res + if isinstance(res, LinkStatus): + links.append(res) + elif type(res) == tuple and len(res) == 4: + links.append(LinkStatus(res[3], res[0], int(res[1]), res[2], pluginname)) + elif type(res) == tuple and len(res) == 5: + links.append(LinkStatus(res[3], res[0], int(res[1]), res[2], pluginname, res[4])) + else: + self.m.log.debug("Invalid getInfo result: " + result) + + # put them on the cache + for link in links: + self.m.infoCache[link.url] = link - cb(pluginname, result) + cb(links) self.m.log.debug("Finished Info Fetching for %s" % pluginname) except Exception, e: self.m.log.warning(_("Info Fetching for %(name)s failed | %(err)s") % {"name": pluginname, "err": str(e)}) - if self.m.core.debug: - print_exc() - - # generate default results - if err: - result = [(url, 0, 3, url) for url in urls] - cb(pluginname, result) - - def decrypt(self, plugin, urls): - self.m.log.debug("Decrypting %s" % plugin) - klass = self.m.core.pluginManager.loadClass("crypter", plugin) - urls = klass.decrypt(self.core, urls) - data, crypter = self.m.core.pluginManager.parseUrls(urls) - return data + crypter + self.core.print_exc()
\ No newline at end of file diff --git a/pyload/threads/ThreadManager.py b/pyload/threads/ThreadManager.py index 9ad23138a..ff8bfe8d7 100644 --- a/pyload/threads/ThreadManager.py +++ b/pyload/threads/ThreadManager.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- ############################################################################### -# Copyright(c) 2008-2012 pyLoad Team +# Copyright(c) 2008-2013 pyLoad Team # http://www.pyload.org # # This file is part of pyLoad. @@ -24,9 +24,8 @@ from time import sleep, time from traceback import print_exc from random import choice -import pycurl - from pyload.datatypes.PyFile import PyFile +from pyload.datatypes.OnlineCheck import OnlineCheck from pyload.network.RequestFactory import getURL from pyload.utils import lock, uniqify from pyload.utils.fs import free_space @@ -63,13 +62,12 @@ class ThreadManager: # pool of ids for online check self.resultIDs = 0 - # threads which are fetching hoster results + # saved online checks self.infoResults = {} + # timeout for cache purge self.timestamp = 0 - pycurl.global_init(pycurl.GLOBAL_DEFAULT) - for i in range(self.core.config.get("download", "max_downloads")): self.createThread() @@ -83,23 +81,26 @@ class ThreadManager: def createInfoThread(self, data, pid): """ start a thread which fetches online status and other info's """ self.timestamp = time() + 5 * 60 - if data: InfoThread(self, data, pid) + if data: InfoThread(self, None, data, pid) @lock - def createResultThread(self, data): + def createResultThread(self, user, data): """ creates a thread to fetch online status, returns result id """ self.timestamp = time() + 5 * 60 rid = self.resultIDs self.resultIDs += 1 + oc = OnlineCheck(rid, user) + self.infoResults[rid] = oc + # maps url to plugin urls = [] for links in data.itervalues(): for link in links: urls.append((link.url, link.plugin)) - InfoThread(self, urls, rid=rid) + InfoThread(self, user, urls, oc=oc) return rid @@ -108,23 +109,13 @@ class ThreadManager: """ Start decrypting of entered data, all links in one package are accumulated to one thread.""" if data: DecrypterThread(self, data, pid) - @lock def getInfoResult(self, rid): - """returns result and clears it""" - self.timestamp = time() + 5 * 60 - # TODO: restrict user to his own results - if rid in self.infoResults: - data = self.infoResults[rid] - self.infoResults[rid] = {} - return data - else: - return {} + return self.infoResults.get(rid) - @lock - def setInfoResults(self, rid, result): - self.core.evm.dispatchEvent("linkcheck:updated", rid, result) - self.infoResults[rid].update(result) + def setInfoResults(self, oc, result): + self.core.evm.dispatchEvent("linkcheck:updated", oc.rid, result, owner=oc.owner) + oc.update(result) def getActiveDownloads(self, user=None): # TODO: user context @@ -220,8 +211,7 @@ class ThreadManager: self.log.warning(_("Failed executing reconnect script!")) self.core.config["reconnect"]["activated"] = False self.reconnecting.clear() - if self.core.debug: - print_exc() + self.core.print_exc() return reconn.wait() @@ -268,6 +258,8 @@ class ThreadManager: """ make a global curl cleanup (currently unused) """ if self.processingIds(): return False + import pycurl + pycurl.global_cleanup() pycurl.global_init(pycurl.GLOBAL_DEFAULT) self.downloaded = 0 @@ -317,7 +309,3 @@ class ThreadManager: if occ not in self.core.files.jobCache: self.core.files.jobCache[occ] = [] self.core.files.jobCache[occ].append(job.id) - - def cleanup(self): - """do global cleanup, should be called when finished with pycurl""" - pycurl.global_cleanup() diff --git a/pyload/utils/__init__.py b/pyload/utils/__init__.py index bc4e27df4..1655be857 100644 --- a/pyload/utils/__init__.py +++ b/pyload/utils/__init__.py @@ -197,7 +197,7 @@ def has_method(obj, name): def accumulate(it, inv_map=None): """ accumulate (key, value) data to {value : [keylist]} dictionary """ - if not inv_map: + if inv_map is None: inv_map = {} for key, value in it: diff --git a/pyload/web/app/scripts/helpers/linkStatus.js b/pyload/web/app/scripts/helpers/linkStatus.js index 2497785fb..448d63691 100644 --- a/pyload/web/app/scripts/helpers/linkStatus.js +++ b/pyload/web/app/scripts/helpers/linkStatus.js @@ -5,7 +5,7 @@ define('helpers/linkStatus', ['underscore', 'handlebars', 'utils/apitypes', 'uti var s; if (status === Api.DownloadStatus.Online) s = '<span class="text-success">' + i18n.gettext('online') + '</span>'; - else if (status === Api.DownloadState.Offline) + else if (status === Api.DownloadStatus.Offline) s = '<span class="text-error">' + i18n.gettext('offline') + '</span>'; else s = '<span class="text-info">' + i18n.gettext('unknown') + '</span>'; diff --git a/pyload/web/app/scripts/models/LinkStatus.js b/pyload/web/app/scripts/models/LinkStatus.js index 2be1ce368..be6385c9c 100644 --- a/pyload/web/app/scripts/models/LinkStatus.js +++ b/pyload/web/app/scripts/models/LinkStatus.js @@ -8,9 +8,10 @@ define(['jquery', 'backbone', 'underscore', 'app', 'utils/apitypes'], defaults: { name: '', - plugin: '', size: -1, - status: Api.DownloadStatus.Queued + status: Api.DownloadStatus.Queued, + plugin: '', + hash: null }, destroy: function() { diff --git a/pyload/web/app/styles/default/linkgrabber.less b/pyload/web/app/styles/default/linkgrabber.less index c2e3642ef..010dbadec 100644 --- a/pyload/web/app/styles/default/linkgrabber.less +++ b/pyload/web/app/styles/default/linkgrabber.less @@ -49,6 +49,11 @@ } + .link-name { + .hyphens(); + width: 50%; + } + img { height: 22px; } diff --git a/pyload/web/app/templates/default/linkgrabber/package.html b/pyload/web/app/templates/default/linkgrabber/package.html index ed699f0d5..d5d4c669b 100644 --- a/pyload/web/app/templates/default/linkgrabber/package.html +++ b/pyload/web/app/templates/default/linkgrabber/package.html @@ -7,9 +7,9 @@ <tbody> {{#each links}} <tr> - <td>{{ name }}</td> + <td class="link-name">{{ name }}</td> <td><img src="{{ pluginIcon plugin }}"> {{ plugin }}</td> - <td>{{formatSize size }}</td> + <td>{{ formatSize size }}</td> <td>{{ linkStatus status }}</td> <td><button class="btn btn-danger btn-mini" data-index={{@index}}><i class="icon-trash"></i></button></td> </tr> |