diff options
Diffstat (limited to 'module')
| -rw-r--r-- | module/Api.py | 2 | ||||
| -rw-r--r-- | module/HookManager.py | 191 | ||||
| -rw-r--r-- | module/interaction/EventManager.py | 151 | ||||
| -rw-r--r-- | module/plugins/Crypter.py | 4 | ||||
| -rw-r--r-- | module/plugins/Hook.py | 32 | ||||
| -rw-r--r-- | module/plugins/internal/MultiHoster.py | 2 | ||||
| -rw-r--r-- | module/threads/DecrypterThread.py | 2 | ||||
| -rw-r--r-- | module/threads/HookThread.py | 9 | ||||
| -rw-r--r-- | module/threads/InfoThread.py | 48 | ||||
| -rw-r--r-- | module/utils/__init__.py | 19 | ||||
| -rw-r--r-- | module/web/pyload_app.py | 1 | 
11 files changed, 210 insertions, 251 deletions
| diff --git a/module/Api.py b/module/Api.py index bafb69408..769fcdf0d 100644 --- a/module/Api.py +++ b/module/Api.py @@ -810,7 +810,7 @@ class Api(Iface):      def getEvents(self, uuid):          """Lists occured events, may be affected to changes in future. -        :param uuid: +        :param uuid: self assigned string uuid which has to be unique          :return: list of `Events`          """          # TODO diff --git a/module/HookManager.py b/module/HookManager.py index 7057b6ee6..41908bc47 100644 --- a/module/HookManager.py +++ b/module/HookManager.py @@ -18,7 +18,7 @@  """  import __builtin__ -import traceback +from traceback import print_exc  from thread import start_new_thread  from threading import RLock @@ -38,11 +38,9 @@ class HookManager:          __builtin__.hookManager = self #needed to let hooks register themself          self.log = self.core.log -        self.plugins = [] -        self.pluginMap = {} -        self.methods = {} #dict of names and list of methods usable by rpc - -        self.events = {} # contains events +        self.plugins = {} +        self.methods = {} # dict of names and list of methods usable by rpc +        self.events = {} # Contains event that will be registred          self.lock = RLock()          self.createIndex() @@ -53,17 +51,19 @@ class HookManager:          # manage hooks an config change          self.addEvent("configChanged", self.manageHooks) -    def try_catch(func): -        def new(*args): +    @lock +    def callInHooks(self, event, *args): +        """  Calls a method in hook and catch / log errors""" +        for plugin in self.plugins.itervalues():              try: +                func = getattr(plugin, event)                  return func(*args)              except Exception, e: -                args[0].log.error(_("Error executing hooks: %s") % str(e)) -                if args[0].core.debug: -                    traceback.print_exc() - -        return new +                plugin.logError(_("Error executing %s" % event), e) +                if self.core.debug: +                    print_exc() +        self.dispatchEvent(event, *args)      def addRPC(self, plugin, func, doc):          plugin = plugin.rpartition(".")[2] @@ -79,14 +79,12 @@ class HookManager:          else:              args = literal_eval(args) -        plugin = self.pluginMap[plugin] +        plugin = self.plugins[plugin]          f = getattr(plugin, func)          return f(*args) - +    @lock      def createIndex(self): -        plugins = [] -          active = []          deactive = [] @@ -97,10 +95,9 @@ class HookManager:                  if self.core.config.get(pluginname, "activated"):                      pluginClass = self.core.pluginManager.loadClass("hooks", pluginname)                      if not pluginClass: continue -                     +                      plugin = pluginClass(self.core, self) -                    plugins.append(plugin) -                    self.pluginMap[pluginClass.__name__] = plugin +                    self.plugins[pluginClass.__name__] = plugin                      if plugin.isActivated():                          active.append(pluginClass.__name__)                  else: @@ -110,15 +107,12 @@ class HookManager:              except:                  self.log.warning(_("Failed activating %(name)s") % {"name": pluginname})                  if self.core.debug: -                    traceback.print_exc() +                    print_exc()          self.log.info(_("Activated plugins: %s") % ", ".join(sorted(active)))          self.log.info(_("Deactivate plugins: %s") % ", ".join(sorted(deactive))) -        self.plugins = plugins -      def manageHooks(self, plugin, name, value): -          # check if section was a plugin          if plugin not in self.core.pluginManager.getPlugins("hooks"):              return @@ -128,12 +122,11 @@ class HookManager:          elif name == "activated" and not value:              self.deactivateHook(plugin) +    @lock      def activateHook(self, plugin): -          #check if already loaded -        for inst in self.plugins: -            if inst.__name__ == plugin: -                return +        if plugin in self.plugins: +            return          pluginClass = self.core.pluginManager.loadClass("hooks", plugin) @@ -142,105 +135,63 @@ class HookManager:          self.log.debug("Plugin loaded: %s" % plugin)          plugin = pluginClass(self.core, self) -        self.plugins.append(plugin) -        self.pluginMap[pluginClass.__name__] = plugin +        self.plugins[pluginClass.__name__] = plugin -        # call core Ready -        start_new_thread(plugin.coreReady, tuple()) +        # active the hook in new thread +        start_new_thread(plugin.activate, tuple()) +    @lock      def deactivateHook(self, plugin): +        if plugin not in self.plugins: +            return +        else: +            hook = self.plugins[plugin] -        hook = None -        for inst in self.plugins: -            if inst.__name__ == plugin: -                hook = inst - -        if not hook: return - -        hook.unload() -        self.log.debug("Plugin unloaded: %s" % plugin) +        hook.deactivate() +        self.log.debug("Plugin deactivated: %s" % plugin)          #remove periodic call          self.log.debug("Removed callback %s" % self.core.scheduler.removeJob(hook.cb)) -        self.plugins.remove(hook) -        del self.pluginMap[hook.__name__] +        del self.plugins[hook.__name__] +        #remove event listener +        for f in dir(hook): +            if f.startswith("__") or type(getattr(hook, f)) != MethodType: +                continue +            self.core.eventManager.removeFromEvents(getattr(hook, f)) -    @try_catch -    def coreReady(self): -        for plugin in self.plugins: +    def activateHooks(self): +        for plugin in self.plugins.itervalues():              if plugin.isActivated(): -                plugin.coreReady() - -        self.dispatchEvent("coreReady") +                plugin.activate() -    @try_catch -    def coreExiting(self): -        for plugin in self.plugins: +    def deactivateHooks(self): +        """  Called when core is shutting down """ +        for plugin in self.plugins.itervalues():              if plugin.isActivated(): -                plugin.coreExiting() - -        self.dispatchEvent("coreExiting") +                plugin.deactivate() -    @lock      def downloadPreparing(self, pyfile): -        for plugin in self.plugins: -            if plugin.isActivated(): -                plugin.downloadPreparing(pyfile) +        self.callInHooks("downloadPreparing", pyfile) -        self.dispatchEvent("downloadPreparing", pyfile) - -    @lock      def downloadFinished(self, pyfile): -        for plugin in self.plugins: -            if plugin.isActivated(): -                if "downloadFinished" in plugin.__threaded__: -                    self.startThread(plugin.downloadFinished, pyfile) -                else: -                    plugin.downloadFinished(pyfile) - -        self.dispatchEvent("downloadFinished", pyfile) +        self.callInHooks("downloadFinished", pyfile) -    @lock -    @try_catch      def downloadFailed(self, pyfile): -        for plugin in self.plugins: -            if plugin.isActivated(): -                if "downloadFailed" in plugin.__threaded__: -                    self.startThread(plugin.downloadFinished, pyfile) -                else: -                    plugin.downloadFailed(pyfile) - -        self.dispatchEvent("downloadFailed", pyfile) +        self.callInHooks("downloadFailed", pyfile) -    @lock      def packageFinished(self, package): -        for plugin in self.plugins: -            if plugin.isActivated(): -                if "packageFinished" in plugin.__threaded__: -                    self.startThread(plugin.packageFinished, package) -                else: -                    plugin.packageFinished(package) - -        self.dispatchEvent("packageFinished", package) +        self.callInHooks("packageFinished", package) -    @lock      def beforeReconnecting(self, ip): -        for plugin in self.plugins: -            plugin.beforeReconnecting(ip) +        self.callInHooks("beforeReconnecting", ip) -        self.dispatchEvent("beforeReconnecting", ip) - -    @lock      def afterReconnecting(self, ip): -        for plugin in self.plugins: -            if plugin.isActivated(): -                plugin.afterReconnecting(ip) - -        self.dispatchEvent("afterReconnecting", ip) +        self.callInHooks("afterReconnecting", ip) +    @lock      def startThread(self, function, *args, **kwargs): -        t = HookThread(self.core.threadManager, function, args, kwargs) +        HookThread(self.core.threadManager, function, args, kwargs)      def activePlugins(self):          """ returns all active plugins """ @@ -249,42 +200,24 @@ class HookManager:      def getAllInfo(self):          """returns info stored by hook plugins"""          info = {} -        for name, plugin in self.pluginMap.iteritems(): +        for name, plugin in self.plugins.iteritems():              if plugin.info:                  #copy and convert so str -                info[name] = dict([(x, str(y) if not isinstance(y, basestring) else y) for x, y in plugin.info.iteritems()]) +                info[name] = dict( +                    [(x, str(y) if not isinstance(y, basestring) else y) for x, y in plugin.info.iteritems()])          return info -      def getInfo(self, plugin):          info = {} -        if plugin in self.pluginMap and self.pluginMap[plugin].info: +        if plugin in self.plugins and self.plugins[plugin].info:              info = dict([(x, str(y) if not isinstance(y, basestring) else y) -                for x, y in self.pluginMap[plugin].info.iteritems()]) +            for x, y in self.plugins[plugin].info.iteritems()])          return info -    def addEvent(self, event, func): -        """Adds an event listener for event name""" -        if event in self.events: -            self.events[event].append(func) -        else: -            self.events[event] = [func] - -    def removeEvent(self, event, func): -        """removes previously added event listener""" -        if event in self.events: -            self.events[event].remove(func) - -    def dispatchEvent(self, event, *args): -        """dispatches event with args""" -        if event in self.events: -            for f in self.events[event]: -                try: -                    f(*args) -                except Exception, e: -                    self.log.warning("Error calling event handler %s: %s, %s, %s" -                    % (event, f, args, str(e))) -                    if self.core.debug: -                        traceback.print_exc() +    def addEvent(self, *args): +        self.core.eventManager.addEvent(*args) + +    def dispatchEvent(self, *args): +        self.core.eventManager.dispatchEvent(*args) diff --git a/module/interaction/EventManager.py b/module/interaction/EventManager.py index 931f50446..38faa3c46 100644 --- a/module/interaction/EventManager.py +++ b/module/interaction/EventManager.py @@ -1,7 +1,7 @@  # -*- coding: utf-8 -*- -from time import time -from module.utils import uniqify +from traceback import print_exc +from time import time  class EventManager:      """ @@ -10,70 +10,119 @@ class EventManager:      **Known Events:**      Most hook methods exists as events. These are some additional known events. -    ===================== ============== ================================== -    Name                     Arguments      Description -    ===================== ============== ================================== -    downloadPreparing     fid            A download was just queued and will be prepared now. -    downloadStarts        fid            A plugin will immediately starts the download afterwards. -    linksAdded            links, pid     Someone just added links, you are able to modify the links. -    allDownloadsProcessed                Every link was handled, pyload would idle afterwards. -    allDownloadsFinished                 Every download in queue is finished. -    unrarFinished         folder, fname  An Unrar job finished -    configChanged         sec,opt,value  The config was changed. -    ===================== ============== ================================== +    ===================== ================ =========================================================== +    Name                      Arguments      Description +    ===================== ================ =========================================================== +    metaEvent             eventName, *args Called for every event, with eventName and orginal args +    downloadPreparing     fid              A download was just queued and will be prepared now. +    downloadStarts        fid              A plugin will immediately starts the download afterwards. +    linksAdded            links, pid       Someone just added links, you are able to modify the links. +    allDownloadsProcessed                  Every link was handled, pyload would idle afterwards. +    allDownloadsFinished                   Every download in queue is finished. +    unrarFinished         folder, fname    An Unrar job finished +    configChanged         sec, opt, value  The config was changed. +    ===================== ================ ===========================================================      | Notes:      |    allDownloadsProcessed is *always* called before allDownloadsFinished.      |    configChanged is *always* called before pluginConfigChanged.      """ + +    CLIENT_EVENTS = ("packageUpdated", "packageInserted", "linkUpdated", "packageDeleted") +      def __init__(self, core):          self.core = core -        self.clients = [] - -    def newClient(self, uuid): -        self.clients.append(Client(uuid)) +        self.log = core.log -    def clean(self): -        for n, client in enumerate(self.clients): -            if client.lastActive + 30 < time(): -                del self.clients[n] +        # uuid : list of events +        self.clients = {} +        self.events = {"metaEvent": []}      def getEvents(self, uuid): -        events = [] -        validUuid = False -        for client in self.clients: -            if client.uuid == uuid: -                client.lastActive = time() -                validUuid = True -                while client.newEvents(): -                    events.append(client.popEvent().toList()) -                break -        if not validUuid: -            self.newClient(uuid) -            events = [ReloadAllEvent("queue").toList(), ReloadAllEvent("collector").toList()] -        return uniqify(events, repr) - -    def addEvent(self, event): -        for client in self.clients: -            client.addEvent(event) - -    def dispatchEvent(self, *args): -        pass +        """ Get accumulated events for uuid since last call, this also registeres new client """ +        if uuid not in self.clients: +            self.clients[uuid] = Client() +        return self.clients[uuid].get() + +    def addEvent(self, event, func): +        """Adds an event listener for event name""" +        if event in self.events: +            if func in self.events[event]: +                self.log.debug("Function already registered %s" % func) +            else: +                self.events[event].append(func) +        else: +            self.events[event] = [func] + +    def removeEvent(self, event, func): +        """removes previously added event listener""" +        if event in self.events: +            self.events[event].remove(func) + +    def dispatchEvent(self, event, *args): +        """dispatches event with args""" +        for f in self.events["metaEvent"]: +            try: +                f(event, *args) +            except Exception, e: +                self.log.warning("Error calling event handler %s: %s, %s, %s" +                % ("metaEvent", f, args, str(e))) +                if self.core.debug: +                    print_exc() + +        if event in self.events: +            for f in self.events[event]: +                try: +                    f(*args) +                except Exception, e: +                    self.log.warning("Error calling event handler %s: %s, %s, %s" +                    % (event, f, args, str(e))) +                    if self.core.debug: +                        print_exc() + +        # append to client event queue +        if event in self.CLIENT_EVENTS: +            for uuid, client in self.clients.items(): +                if client.delete(): +                    del self.clients[uuid] +                else: +                    client.append(event, args) + + +    def removeFromEvents(self, func): +        """ Removes func from all known events """ +        for name, events in self.events.iteritems(): +            if func in events: +                events.remove(func) +  class Client: -    def __init__(self, uuid): -        self.uuid = uuid + +    # delete clients after this time +    TIMEOUT = 60 * 60 +    # max events, if this value is reached you should assume that older events were dropped +    MAX = 30 + +    def __init__(self):          self.lastActive = time()          self.events = [] -    def newEvents(self): -        return len(self.events) > 0 +    def delete(self): +        return self.lastActive + self.TIMEOUT < time() + +    def append(self, event, args): +        ev = (event, args) +        if ev not in self.events: +            self.events.insert(0, ev) -    def popEvent(self): -        if not len(self.events): -            return None -        return self.events.pop(0) +        del self.events[self.MAX:] + + +    def get(self): +        self.lastActive = time() + +        events = self.events +        self.events = [] -    def addEvent(self, event): -        self.events.append(event) +        return [(ev, [str(x) for x in args]) for ev, args in events]
\ No newline at end of file diff --git a/module/plugins/Crypter.py b/module/plugins/Crypter.py index fe7f0deb8..5d164da64 100644 --- a/module/plugins/Crypter.py +++ b/module/plugins/Crypter.py @@ -4,7 +4,7 @@ from traceback import print_exc  from module.Api import Destination  from module.common.packagetools import parseNames -from module.utils import to_list, has_method +from module.utils import to_list, has_method, uniqify  from module.utils.fs import exists, remove, fs_encode  from Base import Base, Retry @@ -83,7 +83,7 @@ class Crypter(Base):              else: # single url                  ret.append(url_or_pack)          # eliminate duplicates -        return set(ret) +        return uniqify(ret)      def __init__(self, core, package=None, password=None):          Base.__init__(self, core) diff --git a/module/plugins/Hook.py b/module/plugins/Hook.py index fe464bdaa..76bc19dbe 100644 --- a/module/plugins/Hook.py +++ b/module/plugins/Hook.py @@ -19,6 +19,9 @@  from traceback import print_exc +from functools import wraps +from module.utils import has_method +  from Base import Base  class Expose(object): @@ -35,6 +38,7 @@ def AddEventListener(event):              return f      return _klass +  class ConfigHandler(object):      """ register method as config handler """      def __new__(cls, f, *args, **kwargs): @@ -42,13 +46,14 @@ class ConfigHandler(object):          return f  def threaded(f): +    @wraps(f)      def run(*args,**kwargs):          hookManager.startThread(f, *args, **kwargs)      return run  class Hook(Base):      """ -    Base class for hook plugins. +    Base class for hook plugins. Please use @threaded decorator for all longer running task.      """      #: automatically register event listeners for functions, attribute will be deleted dont use it yourself @@ -79,16 +84,16 @@ class Hook(Base):              for event, funcs in self.event_map.iteritems():                  if type(funcs) in (list, tuple):                      for f in funcs: -                        self.manager.addEvent(event, getattr(self,f)) +                        self.evm.addEvent(event, getattr(self,f))                  else: -                    self.manager.addEvent(event, getattr(self,funcs)) +                    self.evm.addEvent(event, getattr(self,funcs))              #delete for various reasons              self.event_map = None          if self.event_list:              for f in self.event_list: -                self.manager.addEvent(f, getattr(self,f)) +                self.evm.addEvent(f, getattr(self,f))              self.event_list = None @@ -121,23 +126,16 @@ class Hook(Base):          """ more init stuff if needed """          pass -    def unload(self): -        """ called when hook was deactivated """ -        pass - -    def deactivate(self): -        pass -      def activate(self): -        pass +        """  Used to activate the hook """ +        if has_method(self.__class__, "coreReady"): +            self.logDebug("Deprecated method .coreReady() use activated() instead") +            self.coreReady() -    #event methods - overwrite these if needed     -    def coreReady(self): +    def deactivate(self): +        """ Used to deactivate the hook. """          pass -    def coreExiting(self): -        pass -          def downloadPreparing(self, pyfile):          pass diff --git a/module/plugins/internal/MultiHoster.py b/module/plugins/internal/MultiHoster.py index 872e0b770..1629fdc5f 100644 --- a/module/plugins/internal/MultiHoster.py +++ b/module/plugins/internal/MultiHoster.py @@ -80,6 +80,6 @@ class MultiHoster(Hook):          hoster[self.__name__] = new -    def unload(self): +    def deactivate(self):          for hoster in self.supported:              self.core.pluginManager.restoreState("hoster", hoster)
\ No newline at end of file diff --git a/module/threads/DecrypterThread.py b/module/threads/DecrypterThread.py index a1b7e4f38..8edb97c34 100644 --- a/module/threads/DecrypterThread.py +++ b/module/threads/DecrypterThread.py @@ -4,6 +4,7 @@  from time import sleep  from traceback import print_exc +from module.utils import uniqify  from module.plugins.Base import Retry  from module.plugins.Crypter import Package @@ -54,6 +55,7 @@ class DecrypterThread(BaseThread):              plugin.logDebug("Decrypted", plugin_result)              result.extend(plugin_result) +        result = uniqify(result)          pack_names = {}          urls = [] diff --git a/module/threads/HookThread.py b/module/threads/HookThread.py index fe4a2a651..bffa72ca0 100644 --- a/module/threads/HookThread.py +++ b/module/threads/HookThread.py @@ -2,6 +2,7 @@  # -*- coding: utf-8 -*-  from copy import copy +from traceback import print_exc  from BaseThread import BaseThread @@ -48,6 +49,14 @@ class HookThread(BaseThread):                  del self.kwargs["thread"]                  self.f(*self.args, **self.kwargs) +        except Exception, e: +            if hasattr(self.f, "im_self"): +                hook = self.f.im_self +                hook.logError(_("An Error occured"), e) +                if self.m.core.debug: +                    print_exc() +                    self.writeDebugReport(hook.__name__, plugin=hook) +          finally:              local = copy(self.active)              for x in local: diff --git a/module/threads/InfoThread.py b/module/threads/InfoThread.py index 596153c4b..c1e4458ef 100644 --- a/module/threads/InfoThread.py +++ b/module/threads/InfoThread.py @@ -30,7 +30,7 @@ class InfoThread(BaseThread):          """run method"""          plugins = {} -        container = [] +        crypter = {}          for url, plugin in self.data:              if plugin in plugins: @@ -42,8 +42,7 @@ class InfoThread(BaseThread):          # filter out crypter plugins          for name in self.m.core.pluginManager.getPlugins("crypter"):              if name in plugins: -                container.extend([(name, url) for url in plugins[name]]) - +                crypter[name] = plugins[name]                  del plugins[name]          #directly write to database @@ -60,11 +59,10 @@ class InfoThread(BaseThread):                      self.m.core.files.save()          else: #post the results -            #TODO: finer crypter control -            for name, url in container: +            for name, urls in crypter:                  #attach container content                  try: -                    data = self.decryptContainer(name, url) +                    data = self.decrypt(name, urls)                  except:                      print_exc()                      self.m.log.error("Could not decrypt container.") @@ -169,34 +167,14 @@ class InfoThread(BaseThread):                  cb(pluginname, result) -    def decryptContainer(self, plugin, url): -        data = [] -        # only works on container plugins - -        self.m.log.debug("Pre decrypting %s with %s" % (url, plugin)) - -        # dummy pyfile -        pyfile = PyFile(self.m.core.files, -1, url, url, 0, 0, "", plugin, -1, -1) - -        pyfile.initPlugin() - -        # little plugin lifecycle -        try: -            pyfile.plugin.setup() -            pyfile.plugin.loadToDisk() -            pyfile.plugin.decrypt(pyfile) -            pyfile.plugin.deleteTmp() - -            for pack in pyfile.plugin.packages: -                pyfile.plugin.urls.extend(pack[1]) - -            data, crypter = self.m.core.pluginManager.parseUrls(pyfile.plugin.urls) +    def decrypt(self, plugin, urls): +        self.m.log.debug("Pre decrypting %s" % plugin) +        klass = self.m.core.pluginManager.loadClass("crypter", plugin) -            self.m.log.debug("Got %d links." % len(data)) - -        except Exception, e: -            self.m.log.debug("Pre decrypting error: %s" % str(e)) -        finally: -            pyfile.release() +        # only decrypt files +        if has_method(klass, "decryptFile"): +            urls = p.decrypt(urls) +            data, crypter = self.m.core.pluginManager.parseUrls(urls) +            return data -        return data +        return [] diff --git a/module/utils/__init__.py b/module/utils/__init__.py index a237fde9b..46621c685 100644 --- a/module/utils/__init__.py +++ b/module/utils/__init__.py @@ -72,21 +72,10 @@ def freeSpace(folder):      return free_space(folder) -def uniqify(seq, idfun=None): -# order preserving -    if idfun is None: -        def idfun(x): return x -    seen = {} -    result = [] -    for item in seq: -        marker = idfun(item) -        # in old Python versions: -        # if seen.has_key(marker) -        # but in new ones: -        if marker in seen: continue -        seen[marker] = 1 -        result.append(item) -    return result +def uniqify(seq): #by Dave Kirby +    """ removes duplicates from list, preserve order """ +    seen = set() +    return [x for x in seq if x not in seen and not seen.add(x)]  def parseFileSize(string, unit=None): #returns bytes diff --git a/module/web/pyload_app.py b/module/web/pyload_app.py index a025f6bcb..f73defb45 100644 --- a/module/web/pyload_app.py +++ b/module/web/pyload_app.py @@ -506,6 +506,7 @@ def setup():      return render_to_response('setup.html', {"user": False, "perms": False}) +@login_required("STATUS")  @route("/info")  def info():      conf = PYLOAD.getConfigDict() | 
