diff options
author | Walter Purcaro <vuolter@gmail.com> | 2014-11-15 15:37:20 +0100 |
---|---|---|
committer | Walter Purcaro <vuolter@gmail.com> | 2014-11-15 15:37:20 +0100 |
commit | 63d6eb03440c0f4579d412525e7a951b1f34fd66 (patch) | |
tree | e4adf8ea05a4a77e2405ddfa5dfff2f330111614 | |
parent | Split PluginThread (diff) | |
download | pyload-63d6eb03440c0f4579d412525e7a951b1f34fd66.tar.xz |
Update PluginManager
-rw-r--r-- | pyload/manager/PluginManager.py | 39 | ||||
-rw-r--r-- | pyload/plugins/addon/UpdateManager.py | 304 | ||||
-rw-r--r-- | pyload/plugins/internal/UpdateManager.py | 4 |
3 files changed, 321 insertions, 26 deletions
diff --git a/pyload/manager/PluginManager.py b/pyload/manager/PluginManager.py index cbba0b67f..c4c220af8 100644 --- a/pyload/manager/PluginManager.py +++ b/pyload/manager/PluginManager.py @@ -15,7 +15,7 @@ from SafeEval import const_eval as literal_eval class PluginManager: ROOT = "pyload.plugins." USERROOT = "userplugins." - TYPES = ("account", "addon", "base", "container", "crypter", "hook", "hoster", "internal", "ocr") + TYPES = ("account", "addon", "container", "crypter", "hook", "hoster", "internal", "ocr") PATTERN = re.compile(r'__pattern__\s*=\s*u?r("|\')([^"\']+)') VERSION = re.compile(r'__version__\s*=\s*("|\')([\d.]+)') @@ -41,14 +41,12 @@ class PluginManager: sys.path.append(abspath("")) - #@NOTE: In 0.4.10 directory "accounts" changes to "account" and "hooks" changes to "addon" - self.plugins['accounts'] = self.accountPlugins = self.parse("accounts") - self.plugins['hooks'] = self.hookPlugins = self.parse("hooks") - - for type in set(self.TYPES) - set(('accounts', 'hooks')): + for type in set(self.TYPES): self.plugins[type] = self.parse(type) setattr(self, "%sPlugins" % type, self.plugins[type]) + self.plugins['addon'] = self.addonPlugins.extend(self.hookPlugins) + self.log.debug("Created index of plugins") @@ -155,14 +153,14 @@ class PluginManager: else: config = [list(config)] - if folder not in ("accounts", "internal") and not [True for item in config if item[0] == "activated"]: - config.insert(0, ["activated", "bool", "Activated", False if folder == "addon" else True]) + 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.config.addPluginConfig(name, config, desc) except: self.log.error("Invalid config in %s: %s" % (name, config)) - elif folder == "addon": #force config creation + 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],) @@ -338,28 +336,25 @@ class PluginManager: as_dict = {} for t,n in type_plugins: - if t in ("base", "addon", "internal"): #: do not reload them because would cause to much side effects - continue - elif t in as_dict: + if t in as_dict: as_dict[t].append(n) else: as_dict[t] = [n] for type in as_dict.iterkeys(): - # we do not reload hooks or internals, would cause to much side effects - if type in ("addon", "internal"): - flag = False + if type in ("addon", "internal"): #: do not reload them because would cause to much side effects + self.log.debug("Skipping reload for plugin: [%(type)s] %(name)s" % {'name': plugin, 'type': type}) continue for plugin in as_dict[type]: if plugin in self.plugins[type] and "module" in self.plugins[type][plugin]: - self.log.debug("Reloading plugin: [%(type)s] %(name)s" % {'name': plugin, 'type': type}) + self.log.debug(_("Reloading plugin: [%(type)s] %(name)s") % {'name': plugin, 'type': type}) try: reload(self.plugins[type][plugin]['module']) except Exception, e: - self.log.error("Error when reloading plugin: [%(type)s] %(name)s" % {'name': plugin, 'type': type}, e) + self.log.error(_("Error when reloading plugin: [%(type)s] %(name)s") % {'name': plugin, 'type': type}, e) continue else: @@ -367,19 +362,15 @@ class PluginManager: #index creation self.plugins[type] = self.parse(type) + setattr(self, "%sPlugins" % type, self.plugins[type]) - if type is "accounts": #@TODO: Remove this check in 0.4.10 - self.accountPlugins = self.plugins[type] - else: - setattr(self, "%sPlugins" % type, self.plugins[type]) - - - if "accounts" in as_dict: #: accounts needs to be reloaded + 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 diff --git a/pyload/plugins/addon/UpdateManager.py b/pyload/plugins/addon/UpdateManager.py new file mode 100644 index 000000000..651cdde80 --- /dev/null +++ b/pyload/plugins/addon/UpdateManager.py @@ -0,0 +1,304 @@ +# -*- coding: utf-8 -*- + +import re +import sys + +from operator import itemgetter +from os import path, remove, stat + +from pyload.network.RequestFactory import getURL +from pyload.plugins.internal.Addon import Expose, Addon, threaded +from pyload.utils import safe_join + + +class UpdateManager(Addon): + __name__ = "UpdateManager" + __type__ = "addon" + __version__ = "0.39" + + __config__ = [("activated", "bool", "Activated", True), + ("mode", "pyLoad + plugins;plugins only", "Check updates for", "pyLoad + plugins"), + ("interval", "int", "Check interval in hours", 8), + ("reloadplugins", "bool", "Monitor plugins for code changes (debug mode only)", True), + ("nodebugupdate", "bool", "Don't check for updates in debug mode", True)] + + __description__ = """Check for updates""" + __license__ = "GPLv3" + __authors__ = [("Walter Purcaro", "vuolter@gmail.com")] + + + # event_list = ["pluginConfigChanged"] + + SERVER_URL = "http://updatemanager.pyload.org" + MIN_INTERVAL = 6 * 60 * 60 #: 6h minimum check interval (value is in seconds) + + + def pluginConfigChanged(self, plugin, name, value): + if name == "interval": + interval = value * 60 * 60 + if self.MIN_INTERVAL <= interval != self.interval: + self.core.scheduler.removeJob(self.cb) + self.interval = interval + self.initPeriodical() + else: + self.logDebug("Invalid interval value, kept current") + + elif name == "reloadplugins": + if self.cb2: + self.core.scheduler.removeJob(self.cb2) + if value is True and self.core.debug: + self.periodical2() + + + def coreReady(self): + self.pluginConfigChanged(self.__name__, "interval", self.getConfig("interval")) + x = lambda: self.pluginConfigChanged(self.__name__, "reloadplugins", self.getConfig("reloadplugins")) + self.core.scheduler.addJob(10, x, threaded=False) + + + def unload(self): + self.pluginConfigChanged(self.__name__, "reloadplugins", False) + + + def setup(self): + self.cb2 = None + self.interval = self.MIN_INTERVAL + self.updating = False + self.info = {'pyload': False, 'version': None, 'plugins': False} + self.mtimes = {} #: store modification time for each plugin + + + def periodical2(self): + if not self.updating: + self.autoreloadPlugins() + + self.cb2 = self.core.scheduler.addJob(4, self.periodical2, threaded=False) + + + @Expose + def autoreloadPlugins(self): + """ reload and reindex all modified plugins """ + modules = filter( + lambda m: m and (m.__name__.startswith("pyload.plugins.") or + m.__name__.startswith("userplugins.")) and + m.__name__.count(".") >= 2, sys.modules.itervalues() + ) + + reloads = [] + + for m in modules: + root, type, name = m.__name__.rsplit(".", 2) + id = (type, name) + if type in self.core.pluginManager.plugins: + f = m.__file__.replace(".pyc", ".py") + if not path.isfile(f): + continue + + mtime = stat(f).st_mtime + + if id not in self.mtimes: + self.mtimes[id] = mtime + elif self.mtimes[id] < mtime: + reloads.append(id) + self.mtimes[id] = mtime + + return True if self.core.pluginManager.reloadPlugins(reloads) else False + + + def periodical(self): + if not self.info['pyload'] and not (self.getConfig("nodebugupdate") and self.core.debug): + self.updateThread() + + + def server_request(self): + try: + return getURL(self.SERVER_URL, get={'v': self.core.api.getServerVersion()}).splitlines() + except: + self.logWarning(_("Unable to contact server to get updates")) + + + @threaded + def updateThread(self): + self.updating = True + + status = self.update(onlyplugin=self.getConfig("mode") == "plugins only") + + if status == 2: + self.core.api.restart() + else: + self.updating = False + + + @Expose + def updatePlugins(self): + """ simple wrapper for calling plugin update quickly """ + return self.update(onlyplugin=True) + + + @Expose + def update(self, onlyplugin=False): + """ check for updates """ + data = self.server_request() + + if not data: + exitcode = 0 + + elif data[0] == "None": + self.logInfo(_("No new pyLoad version available")) + updates = data[1:] + exitcode = self._updatePlugins(updates) + + elif onlyplugin: + exitcode = 0 + + else: + newversion = data[0] + self.logInfo(_("*** New pyLoad Version %s available ***") % newversion) + self.logInfo(_("*** Get it here: https://github.com/pyload/pyload/releases ***")) + exitcode = 3 + self.info['pyload'] = True + self.info['version'] = newversion + + return exitcode #: 0 = No plugins updated; 1 = Plugins updated; 2 = Plugins updated, but restart required; 3 = No plugins updated, new pyLoad version available + + + def _updatePlugins(self, updates): + """ check for plugin updates """ + + if self.info['plugins']: + return False #: plugins were already updated + + exitcode = 0 + updated = [] + + vre = re.compile(r'__version__.*=.*("|\')([\d.]+)') + url = updates[0] + schema = updates[1].split('|') + + if "BLACKLIST" in updates: + blacklist = updates[updates.index('BLACKLIST') + 1:] + updates = updates[2:updates.index('BLACKLIST')] + else: + blacklist = None + updates = updates[2:] + + upgradable = sorted(map(lambda x: dict(zip(schema, x.split('|'))), updates), + key=itemgetter("type", "name")) + + for plugin in upgradable: + filename = plugin['name'] + prefix = plugin['type'] + version = plugin['version'] + + if filename.endswith(".pyc"): + name = filename[:filename.find("_")] + else: + name = filename.replace(".py", "") + + #@TODO: obsolete after 0.4.10 + if prefix.endswith("s"): + type = prefix[:-1] + else: + type = prefix + + plugins = getattr(self.core.pluginManager, "%sPlugins" % type) + + oldver = float(plugins[name]['v']) if name in plugins else None + newver = float(version) + + if not oldver: + msg = "New plugin: [%(type)s] %(name)s (v%(newver).2f)" + elif newver > oldver: + msg = "New version of plugin: [%(type)s] %(name)s (v%(oldver).2f -> v%(newver).2f)" + else: + continue + + self.logInfo(_(msg) % {'type' : type, + 'name' : name, + 'oldver': oldver, + 'newver': newver}) + try: + content = getURL(url % plugin) + m = vre.search(content) + + if m and m.group(2) == version: + f = open(safe_join("userplugins", prefix, filename), "wb") + f.write(content) + f.close() + updated.append((prefix, name)) + else: + raise Exception, _("Version mismatch") + + except Exception, e: + self.logError(_("Error updating plugin %s") % filename, e) + + if blacklist: + blacklisted = map(lambda x: (x.split('|')[0], x.split('|')[1].rsplit('.', 1)[0]), blacklist) + + # Always protect internal plugins from removing + for i, n, t in blacklisted.enumerate(): + if t == "internal": + del blacklisted[i] + + blacklisted = sorted(blacklisted) + removed = self.removePlugins(blacklisted) + for t, n in removed: + self.logInfo(_("Removed blacklisted plugin [%(type)s] %(name)s") % { + 'type': t, + 'name': n, + }) + + if updated: + reloaded = self.core.pluginManager.reloadPlugins(updated) + if reloaded: + self.logInfo(_("Plugins updated and reloaded")) + exitcode = 1 + else: + self.logInfo(_("*** Plugins have been updated, but need a pyLoad restart to be reloaded ***")) + self.info['plugins'] = True + exitcode = 2 + else: + self.logInfo(_("No plugin updates available")) + + return exitcode #: 0 = No plugins updated; 1 = Plugins updated; 2 = Plugins updated, but restart required + + + @Expose + def removePlugins(self, type_plugins): + """ delete plugins from disk """ + + if not type_plugins: + return + + self.logDebug("Requested deletion of plugins: %s" % type_plugins) + + removed = [] + + for type, name in type_plugins: + err = False + file = name + ".py" + + for root in ("userplugins", path.join(pypath, "pyload", "plugins")): + + filename = safe_join(root, type, file) + try: + remove(filename) + except Exception, e: + self.logDebug("Error deleting: %s" % path.basename(filename), e) + err = True + + filename += "c" + if path.isfile(filename): + try: + if type == "addon": + self.manager.deactivateAddon(name) + remove(filename) + except Exception, e: + self.logDebug("Error deleting: %s" % path.basename(filename), e) + err = True + + if not err: + id = (type, name) + removed.append(id) + + return removed #: return a list of the plugins successfully removed diff --git a/pyload/plugins/internal/UpdateManager.py b/pyload/plugins/internal/UpdateManager.py index f1312dda5..651cdde80 100644 --- a/pyload/plugins/internal/UpdateManager.py +++ b/pyload/plugins/internal/UpdateManager.py @@ -235,9 +235,9 @@ class UpdateManager(Addon): if blacklist: blacklisted = map(lambda x: (x.split('|')[0], x.split('|')[1].rsplit('.', 1)[0]), blacklist) - # Always protect base and internal plugins from removing + # Always protect internal plugins from removing for i, n, t in blacklisted.enumerate(): - if t in ("base", "internal"): + if t == "internal": del blacklisted[i] blacklisted = sorted(blacklisted) |