summaryrefslogtreecommitdiffstats
path: root/pyload
diff options
context:
space:
mode:
Diffstat (limited to 'pyload')
-rw-r--r--pyload/Core.py4
-rw-r--r--pyload/api/DownloadPreparingApi.py18
-rw-r--r--pyload/datatypes/OnlineCheck.py33
-rw-r--r--pyload/datatypes/PyFile.py6
-rw-r--r--pyload/plugins/Base.py30
-rw-r--r--pyload/plugins/Crypter.py77
-rw-r--r--pyload/plugins/Hoster.py6
-rw-r--r--pyload/plugins/network/CurlRequest.py1
-rw-r--r--pyload/remote/apitypes.py7
-rw-r--r--pyload/remote/apitypes_debug.py2
-rw-r--r--pyload/remote/pyload.thrift7
-rw-r--r--pyload/remote/wsbackend/AsyncHandler.py2
-rw-r--r--pyload/threads/BaseThread.py41
-rw-r--r--pyload/threads/DecrypterThread.py61
-rw-r--r--pyload/threads/InfoThread.py199
-rw-r--r--pyload/threads/ThreadManager.py46
-rw-r--r--pyload/utils/__init__.py2
-rw-r--r--pyload/web/app/scripts/helpers/linkStatus.js2
-rw-r--r--pyload/web/app/scripts/models/LinkStatus.js5
-rw-r--r--pyload/web/app/styles/default/linkgrabber.less5
-rw-r--r--pyload/web/app/templates/default/linkgrabber/package.html4
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>