# -*- coding: utf-8 -*- ############################################################################### # 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 __builtin__ from gettext import gettext from thread import start_new_thread from threading import RLock from collections import defaultdict from new_collections import namedtuple from types import MethodType from pyload.Api import AddonService, AddonInfo, ServiceException, ServiceDoesNotExist from pyload.threads.AddonThread import AddonThread from utils import lock, to_string AddonTuple = namedtuple('AddonTuple', 'instances events handler') class AddonManager: """ Manages addons, loading, unloading. """ def __init__(self, core): self.core = core self.config = self.core.config __builtin__.addonManager = self #needed to let addons register themselves self.log = self.core.log # TODO: multiuser addons # maps plugin names to info tuple self.plugins = defaultdict(lambda: AddonTuple([], [], {})) # Property hash mapped to meta data self.info_props = {} self.lock = RLock() self.createIndex() # manage addons on config change self.listenTo("config:changed", self.manageAddon) def iterAddons(self): """ Yields (name, meta_data) of all addons """ return self.plugins.iteritems() @lock def callInHooks(self, event, eventName, *args): """ Calls a method in all addons and catch / log errors""" for plugin in self.plugins.itervalues(): for inst in plugin.instances: self.call(inst, event, *args) self.dispatchEvent(eventName, *args) def call(self, plugin, f, *args): try: func = getattr(plugin, f) return func(*args) except Exception, e: plugin.logError(_("Error when executing %s" % f), e) self.core.print_exc() def invoke(self, plugin, func_name, args): """ Invokes a registered method """ if plugin not in self.plugins and func_name not in self.plugins[plugin].handler: raise ServiceDoesNotExist(plugin, func_name) # TODO: choose correct instance try: func = getattr(self.plugins[plugin].instances[0], func_name) return func(*args) except Exception, e: raise ServiceException(e.message) @lock def createIndex(self): active = [] deactive = [] for pluginname in self.core.pluginManager.getPlugins("addons"): try: # check first for builtin plugin attrs = self.core.pluginManager.loadAttributes("addons", pluginname) internal = attrs.get("internal", False) if internal or self.core.config.get(pluginname, "activated"): pluginClass = self.core.pluginManager.loadClass("addons", pluginname) if not pluginClass: continue plugin = pluginClass(self.core, self) self.plugins[pluginClass.__name__].instances.append(plugin) # hide internals from printing if not internal and plugin.isActivated(): active.append(pluginClass.__name__) else: self.log.debug("Loaded internal plugin: %s" % pluginClass.__name__) else: deactive.append(pluginname) except: self.log.warning(_("Failed activating %(name)s") % {"name": pluginname}) self.core.print_exc() self.log.info(_("Activated addons: %s") % ", ".join(sorted(active))) self.log.info(_("Deactivated addons: %s") % ", ".join(sorted(deactive))) def manageAddon(self, plugin, name, value): # TODO: multi user # check if section was a plugin if plugin not in self.core.pluginManager.getPlugins("addons"): return if name == "activated" and value: self.activateAddon(plugin) elif name == "activated" and not value: self.deactivateAddon(plugin) @lock def activateAddon(self, plugin): #check if already loaded if plugin in self.plugins: return pluginClass = self.core.pluginManager.loadClass("addons", plugin) if not pluginClass: return self.log.debug("Plugin loaded: %s" % plugin) plugin = pluginClass(self.core, self) self.plugins[pluginClass.__name__].instances.append(plugin) # active the addon in new thread start_new_thread(plugin.activate, tuple()) self.registerEvents() @lock def deactivateAddon(self, plugin): if plugin not in self.plugins: return else: # todo: multiple instances addon = self.plugins[plugin].instances[0] if addon.__internal__: return self.call(addon, "deactivate") self.log.debug("Plugin deactivated: %s" % plugin) #remove periodic call self.log.debug("Removed callback %s" % self.core.scheduler.removeJob(addon.cb)) # todo: only delete instances, meta data is lost otherwise del self.plugins[addon.__name__].instances[:] # TODO: could be improved #remove event listener for f in dir(addon): if f.startswith("__") or type(getattr(addon, f)) != MethodType: continue self.core.eventManager.removeFromEvents(getattr(addon, f)) def activateAddons(self): self.log.info(_("Activating addons...")) for plugin in self.plugins.itervalues(): for inst in plugin.instances: if inst.isActivated(): self.call(inst, "activate") self.registerEvents() def deactivateAddons(self): """ Called when core is shutting down """ self.log.info(_("Deactivating addons...")) for plugin in self.plugins.itervalues(): for inst in plugin.instances: self.call(inst, "deactivate") def downloadPreparing(self, pyfile): self.callInHooks("downloadPreparing", "download:preparing", pyfile) def downloadFinished(self, pyfile): self.callInHooks("downloadFinished", "download:finished", pyfile) def downloadFailed(self, pyfile): self.callInHooks("downloadFailed", "download:failed", pyfile) def packageFinished(self, package): self.callInHooks("packageFinished", "package:finished", package) @lock def startThread(self, function, *args, **kwargs): AddonThread(self.core.threadManager, function, args, kwargs) def activePlugins(self): """ returns all active plugins """ return [p for x in self.plugins.values() for p in x.instances if p.isActivated()] def getInfo(self, plugin): """ Retrieves all info data for a plugin """ data = [] # TODO if plugin in self.plugins: if plugin.instances: for attr in dir(plugin.instances[0]): if attr.startswith("__Property"): info = self.info_props[attr] info.value = getattr(plugin.instances[0], attr) data.append(info) return data def addEventListener(self, plugin, func, event): """ add the event to the list """ self.plugins[plugin].events.append((func, event)) def registerEvents(self): """ actually register all saved events """ for name, plugin in self.plugins.iteritems(): for func, event in plugin.events: for inst in plugin.instances: self.listenTo(event, getattr(inst, func)) def addAddonHandler(self, plugin, func, label, desc, args, package, media): """ Registers addon service description """ self.plugins[plugin].handler[func] = AddonService(func, gettext(label), gettext(desc), args, package, media) def addInfoProperty(self, h, name, desc): """ Register property as :class:`AddonInfo` """ self.info_props[h] = AddonInfo(name, desc) def listenTo(self, *args): self.core.eventManager.listenTo(*args) def dispatchEvent(self, *args): self.core.eventManager.dispatchEvent(*args)