# -*- coding: utf-8 -*- from __future__ import with_statement import re import sys from itertools import chain from os import listdir, makedirs from os.path import isdir, isfile, join, exists, abspath from sys import version_info from traceback import print_exc from urllib import unquote from SafeEval import const_eval as literal_eval class PluginManager(object): ROOT = "pyload.plugin." USERROOT = "userplugins." TYPES = ["account", "addon", "container", "crypter", "extractor", "hook", "hoster", "internal", "ocr"] PATTERN = re.compile(r'__pattern\s*=\s*u?r("|\')([^"\']+)') VERSION = re.compile(r'__version\s*=\s*("|\')([\d.]+)') CONFIG = re.compile(r'__config\s*=\s*\[([^\]]+)', re.M) DESC = re.compile(r'__description\s*=\s*("|"""|\')([^"\']+)') def __init__(self, core): self.core = core self.plugins = {} self.createIndex() # register for import addon sys.meta_path.append(self) def loadTypes(self): rootdir = join(pypath, "pyload", "plugin") userdir = "userplugins" types = set().union(*[[d for d in listdir(p) if isdir(join(p, d))] for p in (rootdir, userdir) if exists(p)]) if not types: self.core.log.critical(_("No plugins found!")) self.TYPES = list(set(self.TYPES) | types) def createIndex(self): """create information for all plugins available""" sys.path.append(abspath("")) self.loadTypes() configs = [] for type in self.TYPES: self.plugins[type] = self.parse(type) setattr(self, "%sPlugins" % type, self.plugins[type]) configs.extend("%s_%s" % (p, type) for p in self.plugins[type]) self.core.config.removeDeletedPlugins(configs) self.core.log.debug("Created index of plugins") def parse(self, folder, rootplugins={}): """ returns dict with information home contains parsed plugins from pyload. """ plugins = {} if rootplugins: try: pfolder = join("userplugins", folder) if not exists(pfolder): makedirs(pfolder) for ifile in (join("userplugins", "__init__.py"), join(pfolder, "__init__.py")): if not exists(ifile): f = open(ifile, "wb") f.close() except IOError, e: self.core.log.critical(str(e)) return rootplugins else: pfolder = join(pypath, "pyload", "plugin", folder) for f in listdir(pfolder): if isfile(join(pfolder, f)) and f.endswith(".py") and not f.startswith("_"): try: with open(join(pfolder, f)) as data: content = data.read() except IOError, e: self.core.log.error(str(e)) continue name = f[:-3] if name[-1] == ".": name = name[:-4] version = self.VERSION.findall(content) if version: version = float(version[0][1]) else: version = 0 if rootplugins and name in rootplugins: if rootplugins[name]['version'] >= version: continue plugins[name] = {} plugins[name]['version'] = version module = f.replace(".pyc", "").replace(".py", "") # the plugin is loaded from user directory plugins[name]['user'] = True if rootplugins else False plugins[name]['name'] = module pattern = self.PATTERN.findall(content) if pattern: pattern = pattern[0][1] try: regexp = re.compile(pattern) except Exception: self.core.log.error(_("%s has a invalid pattern") % name) pattern = r'^unmatchable$' regexp = re.compile(pattern) plugins[name]['pattern'] = pattern plugins[name]['re'] = regexp # internals have no config if folder == "internal": self.core.config.deleteConfig("internal") continue config = self.CONFIG.findall(content) if config: try: config = literal_eval(config[0].strip().replace("\n", "").replace("\r", "")) desc = self.DESC.findall(content) desc = desc[0][1] if desc else "" if type(config[0]) == tuple: config = [list(x) for x in config] else: config = [list(config)] if folder not in ("account", "internal") and not [True for item in config if item[0] == "activated"]: config.insert(0, ["activated", "bool", "Activated", False if folder in ("addon", "hook") else True]) self.core.config.addPluginConfig("%s_%s" % (name, folder), config, desc) except Exception: self.core.log.error("Invalid config in %s: %s" % (name, config)) elif folder in ("addon", "hook"): # force config creation desc = self.DESC.findall(content) desc = desc[0][1] if desc else "" config = (["activated", "bool", "Activated", False],) try: self.core.config.addPluginConfig("%s_%s" % (name, folder), config, desc) except Exception: self.core.log.error("Invalid config in %s: %s" % (name, config)) if not rootplugins and plugins: #: Double check plugins.update(self.parse(folder, plugins)) return plugins def parseUrls(self, urls): """parse plugins for given list of urls""" last = None res = [] #: tupels of (url, plugintype, pluginname) for url in urls: if type(url) not in (str, unicode, buffer): continue url = unquote(url) if last and last[2]['re'].match(url): res.append((url, last[0], last[1])) continue for plugintype in self.TYPES: m = None for name, plugin in self.plugins[plugintype].iteritems(): try: if 'pattern' in plugin: m = plugin['re'].match(url) except KeyError: self.core.log.error(_("Plugin [%(type)s] %(name)s skipped due broken pattern") % {'name': plugin['name'], 'type': plugintype}) if m: res.append((url, plugintype, name)) last = (plugintype, name, plugin) break if m: break else: res.append((url, "internal", "BasePlugin")) print res return res def findPlugin(self, type, name): if type not in self.plugins: return None elif name not in self.plugins[type]: self.core.log.warning(_("Plugin [%(type)s] %(name)s not found | Using plugin: [internal] BasePlugin") % {'name': name, 'type': type}) return self.internalPlugins['BasePlugin'] else: return self.plugins[type][name] def getPlugin(self, type, name, original=False): """return plugin module from hoster|decrypter|container""" plugin = self.findPlugin(type, name) if plugin is None: return {} if "new_module" in plugin and not original: return plugin['new_module'] else: return self.loadModule(type, name) def getPluginName(self, type, name): """ used to obtain new name if other plugin was injected""" plugin = self.findPlugin(type, name) if plugin is None: return "" if "new_name" in plugin: return plugin['new_name'] return name def loadModule(self, type, name): """ Returns loaded module for plugin :param type: plugin type, subfolder of pyload.plugins :param name: """ plugins = self.plugins[type] if name in plugins: if "module" in plugins[name]: return plugins[name]['module'] try: module = __import__(self.ROOT + "%s.%s" % (type, plugins[name]['name']), globals(), locals(), plugins[name]['name']) except Exception, e: self.core.log.error(_("Error importing plugin: [%(type)s] %(name)s (v%(version).2f) | %(errmsg)s") % {'name': name, 'type': type, 'version': plugins[name]['version'], "errmsg": str(e)}) if self.core.debug: print_exc() else: plugins[name]['module'] = module # : cache import, maybe unneeded self.core.log.debug(_("Loaded plugin: [%(type)s] %(name)s (v%(version).2f)") % {'name': name, 'type': type, 'version': plugins[name]['version']}) return module def loadClass(self, type, name): """Returns the class of a plugin with the same name""" module = self.loadModule(type, name) if module: return getattr(module, name) else: return None def getAccountPlugins(self): """return list of account plugin names""" return self.accountPlugins.keys() def find_module(self, fullname, path=None): # redirecting imports if necesarry if fullname.startswith(self.ROOT) or fullname.startswith(self.USERROOT): # seperate pyload plugins if fullname.startswith(self.USERROOT): user = 1 else: user = 0 # used as bool and int split = fullname.split(".") if len(split) != 4 - user: return type, name = split[2 - user:4 - user] if type in self.plugins and name in self.plugins[type]: # userplugin is a newer version if not user and self.plugins[type][name]['user']: return self # imported from userdir, but pyloads is newer if user and not self.plugins[type][name]['user']: return self def load_module(self, name, replace=True): if name not in sys.modules: # could be already in modules if replace: if self.ROOT in name: newname = name.replace(self.ROOT, self.USERROOT) else: newname = name.replace(self.USERROOT, self.ROOT) else: newname = name base, plugin = newname.rsplit(".", 1) self.core.log.debug("Redirected import %s -> %s" % (name, newname)) module = __import__(newname, globals(), locals(), [plugin]) # inject under new an old name sys.modules[name] = module sys.modules[newname] = module return sys.modules[name] def reloadPlugins(self, type_plugins): """ reload and reindex plugins """ if not type_plugins: return None self.core.log.debug("Request reload of plugins: %s" % type_plugins) reloaded = [] as_dict = {} for t, n in type_plugins: if t in as_dict: as_dict[t].append(n) else: as_dict[t] = [n] for type in as_dict.iterkeys(): if type in ("addon", "internal"): # : do not reload them because would cause to much side effects self.core.log.debug("Skipping reload for plugins from type: %(type)s" % {'type': type}) continue for plugin in as_dict[type]: if plugin in self.plugins[type] and "module" in self.plugins[type][plugin]: self.core.log.debug(_("Reloading plugin: [%(type)s] %(name)s") % {'name': plugin, 'type': type}) try: reload(self.plugins[type][plugin]['module']) except Exception, e: self.core.log.error(_("Error when reloading plugin: [%(type)s] %(name)s") % {'name': plugin, 'type': type}, e) continue else: reloaded.append((type, plugin)) # index creation self.plugins[type] = self.parse(type) setattr(self, "%sPlugins" % type, self.plugins[type]) if "account" in as_dict: #: accounts needs to be reloaded self.core.accountManager.initPlugins() self.core.scheduler.addJob(0, self.core.accountManager.getAccountInfos) return reloaded #: return a list of the plugins successfully reloaded def reloadPlugin(self, type_plugin): """ reload and reindex ONE plugin """ return True if self.reloadPlugins(type_plugin) else False