diff options
Diffstat (limited to 'portato')
-rw-r--r-- | portato/backend/portage/__init__.py | 12 | ||||
-rw-r--r-- | portato/backend/portage/package.py | 9 | ||||
-rw-r--r-- | portato/backend/portage/package_22.py (renamed from portato/plugins/gpytage.py) | 14 | ||||
-rw-r--r-- | portato/backend/portage/settings_22.py | 27 | ||||
-rw-r--r-- | portato/backend/portage/system.py | 13 | ||||
-rw-r--r-- | portato/backend/portage/system_22.py | 34 | ||||
-rw-r--r-- | portato/gui/dialogs.py | 7 | ||||
-rw-r--r-- | portato/gui/windows/mailinfo.py | 30 | ||||
-rw-r--r-- | portato/gui/windows/main.py | 19 | ||||
-rw-r--r-- | portato/gui/windows/plugin.py | 5 | ||||
-rw-r--r-- | portato/plugin.py | 636 | ||||
-rw-r--r-- | portato/plugins/__init__.py | 7 | ||||
-rw-r--r-- | portato/plugins/etc_proposals.py | 31 | ||||
-rw-r--r-- | portato/plugins/notify.py | 22 |
14 files changed, 331 insertions, 535 deletions
diff --git a/portato/backend/portage/__init__.py b/portato/backend/portage/__init__.py index 6ccbf7f..be6cce6 100644 --- a/portato/backend/portage/__init__.py +++ b/portato/backend/portage/__init__.py @@ -12,5 +12,13 @@ from __future__ import absolute_import -from .system import PortageSystem -from .package import PortagePackage +from portage import VERSION as PV + +VERSION = tuple(map(int, (x.split("_")[0] for x in PV.split(".")))) + +if VERSION >= (2, 2): + from .system_22 import PortageSystem_22 as PortageSystem + from .package_22 import PortagePackage_22 as PortagePackage +else: + from .system import PortageSystem + from .package import PortagePackage diff --git a/portato/backend/portage/package.py b/portato/backend/portage/package.py index 3502306..78125ee 100644 --- a/portato/backend/portage/package.py +++ b/portato/backend/portage/package.py @@ -18,7 +18,12 @@ from .. import system from ..exceptions import BlockedException, PackageNotFoundException, DependencyCalcError from ...helper import debug, error, unique_array -import portage, portage_dep +import portage + +try: + import portage.dep as portage_dep +except ImportError: + import portage_dep import os.path @@ -221,7 +226,7 @@ class PortagePackage (Package): for dep in deps: if dep[0] == '!': # blocking sth - blocked = system.find_packages(dep, "installed", only_cpv = True) + blocked = system.find_packages(dep, "installed") if len(blocked) == 1: # only exact one match allowed to be harmless if blocked[0].get_slot_cp() == self.get_slot_cp(): # blocks in the same slot are harmless continue diff --git a/portato/plugins/gpytage.py b/portato/backend/portage/package_22.py index 22cc7ef..4fe03d9 100644 --- a/portato/plugins/gpytage.py +++ b/portato/backend/portage/package_22.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# File: portato/plugins/gpytage.py +# File: portato/backend/portage/package_22.py # This file is part of the Portato-Project, a graphical portage-frontend. # # Copyright (C) 2008 René 'Necoro' Neumann @@ -10,7 +10,13 @@ # # Written by René 'Necoro' Neumann <necoro@necoro.net> -from subprocess import Popen +from __future__ import absolute_import, with_statement -def gpytage(*args, **kwargs): - Popen(["/usr/bin/gpytage"]) +from .package import PortagePackage + +class PortagePackage_22 (PortagePackage): + """ + The 2.2 specialization of the portage package. + Currently this is identical to the normal one. + """ + pass diff --git a/portato/backend/portage/settings_22.py b/portato/backend/portage/settings_22.py new file mode 100644 index 0000000..a43d69e --- /dev/null +++ b/portato/backend/portage/settings_22.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# +# File: portato/backend/portage/settings_22.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2008 René 'Necoro' Neumann +# This is free software. You may redistribute copies of it under the terms of +# the GNU General Public License version 2. +# There is NO WARRANTY, to the extent permitted by law. +# +# Written by René 'Necoro' Neumann <necoro@necoro.net> + +from __future__ import absolute_import + +import portage.sets +from .settings import PortageSettings + +class PortageSettings_22 (PortageSettings): + """Enhances the normal PortageSettings in ways, that it adds the setsconfig.""" + + def __init__ (self): + PortageSettings.__init__(self) + + def load (self): + PortageSettings.load(self) + + self.setsconfig = portage.sets.load_default_config(self.settings, self.trees[self.settings["ROOT"]]) diff --git a/portato/backend/portage/system.py b/portato/backend/portage/system.py index 0d81945..feaf4df 100644 --- a/portato/backend/portage/system.py +++ b/portato/backend/portage/system.py @@ -16,6 +16,7 @@ import re, os, os.path import portage from collections import defaultdict +from . import VERSION from .package import PortagePackage from .settings import PortageSettings from ..system_interface import SystemInterface @@ -36,8 +37,6 @@ class PortageSystem (SystemInterface): self.use_descs = {} self.local_use_descs = defaultdict(dict) - self._version = tuple([x.split("_")[0] for x in portage.VERSION.split(".")]) - def get_version (self): return "Portage %s" % portage.VERSION @@ -142,7 +141,7 @@ class PortageSystem (SystemInterface): """ if not only_cpv: - return [PortagePackage(x) for x in list_of_packages] + return [self.new_package(x) for x in list_of_packages] else: return list_of_packages @@ -154,7 +153,7 @@ class PortageSystem (SystemInterface): if only_cpv: return portage.best(list) else: - return PortagePackage(portage.best(list)) + return self.new_package(portage.best(list)) def find_best_match (self, search_key, masked = False, only_installed = False, only_cpv = False): t = [] @@ -166,7 +165,7 @@ class PortageSystem (SystemInterface): t = self.find_packages(search_key, pkgSet = pkgSet, masked = masked, with_version = True, only_cpv = True) - if self._version >= (2,1,5): + if VERSION >= (2,1,5): t += [pkg.get_cpv() for pkg in self.find_packages(search_key, "installed") if not (pkg.is_testing(True) or pkg.is_masked())] else: t = self.find_packages(search_key, "installed", only_cpv=True) @@ -287,7 +286,7 @@ class PortageSystem (SystemInterface): unresolved = [] for x in list: cpv = x.strip() - if len(cpv) and check(cpv): + if cpv and check(cpv): pkg = self.find_best_match(cpv, only_cpv = only_cpv) if pkg: resolved.append(pkg) @@ -304,7 +303,7 @@ class PortageSystem (SystemInterface): return portage.catpkgsplit(cpv) def sort_package_list(self, pkglist): - pkglist.sort(PortagePackage.compare_version) + pkglist.sort(PortagePackage.compare_version) # XXX: waaah ... direct package naming... =/ return pkglist def reload_settings (self): diff --git a/portato/backend/portage/system_22.py b/portato/backend/portage/system_22.py new file mode 100644 index 0000000..be27186 --- /dev/null +++ b/portato/backend/portage/system_22.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# +# File: portato/backend/portage/system_22.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2008 René 'Necoro' Neumann +# This is free software. You may redistribute copies of it under the terms of +# the GNU General Public License version 2. +# There is NO WARRANTY, to the extent permitted by law. +# +# Written by René 'Necoro' Neumann <necoro@necoro.net> + +from __future__ import absolute_import, with_statement + +import os +import portage + +from collections import defaultdict + +from .package_22 import PortagePackage_22 +from .settings_22 import PortageSettings_22 +from .system import PortageSystem + +class PortageSystem_22 (PortageSystem): + + def __init__ (self): + self.settings = PortageSettings_22() + portage.WORLD_FILE = os.path.join(self.settings.settings["ROOT"],portage.WORLD_FILE) + + self.use_descs = {} + self.local_use_descs = defaultdict(dict) + + def new_package (self, cpv): + return PortagePackage_22(cpv) diff --git a/portato/gui/dialogs.py b/portato/gui/dialogs.py index 8f0c78c..7f8a736 100644 --- a/portato/gui/dialogs.py +++ b/portato/gui/dialogs.py @@ -13,6 +13,13 @@ import gtk from ..helper import error +def mail_failure_dialog(e): + dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Mail could not be sent")) + dialog.format_secondary_text(_("The error was: %s") % e) + ret = dialog.run() + dialog.destroy() + return ret + def queue_not_empty_dialog(): dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_NONE, _("There are some packages in the emerge queue and/or an emerge process is running.\nDo you really want to quit?")) #dialog.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_YES, gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) diff --git a/portato/gui/windows/mailinfo.py b/portato/gui/windows/mailinfo.py index 8e476c6..1b5735a 100644 --- a/portato/gui/windows/mailinfo.py +++ b/portato/gui/windows/mailinfo.py @@ -12,11 +12,12 @@ from __future__ import absolute_import -import smtplib +import smtplib, socket import time from .basic import AbstractDialog from ..utils import GtkThread +from ..dialogs import mail_failure_dialog from ...helper import debug, info from ...constants import VERSION @@ -59,20 +60,23 @@ Subject: %s self.message = message def send (self): - debug("Connecting to server") - server = smtplib.SMTP("mail.necoro.eu") - debug("Sending mail") try: + debug("Connecting to server") + server = smtplib.SMTP("mail.necoro.eu") + debug("Sending mail") try: - server.sendmail(self.addr, self.TO, self.message) - except smtplib.SMTPRecipientsRefused, e: - info(_("An error occurred while sending. I think we where greylisted. The error: %s") % e) - info(_("Wait 60 seconds and try again.")) - time.sleep(60) - server.sendmail(self.addr, self.TO, self.message) - debug("Sent") - finally: - server.quit() + try: + server.sendmail(self.addr, self.TO, self.message) + except smtplib.SMTPRecipientsRefused, e: + info(_("An error occurred while sending. I think we where greylisted. The error: %s") % e) + info(_("Wait 60 seconds and try again.")) + time.sleep(60) + server.sendmail(self.addr, self.TO, self.message) + debug("Sent") + finally: + server.quit() + except socket.error, e: + mail_failure_dialog("%s (Code: %s)" % (e.args[1], e.args[0])) def cb_cancel_clicked (self, *args): diff --git a/portato/gui/windows/main.py b/portato/gui/windows/main.py index 364810d..b4e6353 100644 --- a/portato/gui/windows/main.py +++ b/portato/gui/windows/main.py @@ -607,13 +607,13 @@ class MainWindow (Window): # set plugins and plugin-menu splash(_("Loading Plugins")) - plugin.load_plugins("gtk") - menus = plugin.get_plugin_queue().get_plugin_menus() + plugin.load_plugins() + menus = [p.menus for p in plugin.get_plugin_queue().get_plugins()] if menus: self.tree.get_widget("pluginMenuItem").set_no_show_all(False) pluginMenu = self.tree.get_widget("pluginMenu") - for m in menus: + for m in itt.chain(*menus): item = gtk.MenuItem(m.label) item.connect("activate", m.call) pluginMenu.append(item) @@ -1082,13 +1082,8 @@ class MainWindow (Window): def save_plugin (p): def _save (): - stat_on = p.status >= p.STAT_ENABLED - hard_on = not p.get_option("disabled") - - if stat_on != hard_on: - return int(stat_on) - else: - return "" + return int(p.status >= p.STAT_ENABLED) + return _save # SESSION VERSION @@ -1553,8 +1548,8 @@ class MainWindow (Window): if queue is None: plugins = [] else: - plugins = queue.get_plugins() - + plugins = list(queue.get_plugins()) + PluginWindow(self.window, plugins) return True diff --git a/portato/gui/windows/plugin.py b/portato/gui/windows/plugin.py index 17c5326..9760658 100644 --- a/portato/gui/windows/plugin.py +++ b/portato/gui/windows/plugin.py @@ -15,6 +15,7 @@ from __future__ import absolute_import import gtk from .basic import AbstractDialog +from ...backend import system from ...helper import debug class PluginWindow (AbstractDialog): @@ -64,7 +65,7 @@ class PluginWindow (AbstractDialog): self.window.show_all() def build_dep_list (self): - store = gtk.TreeStore(gtk.gdk.Pixbuf, str) + store = gtk.ListStore(gtk.gdk.Pixbuf, str) self.depList.set_model(store) @@ -113,7 +114,7 @@ class PluginWindow (AbstractDialog): if not plugin.description: self.descrLabel.hide() else: - self.descrLabel.set_label(plugin.description) + self.descrLabel.set_markup(plugin.description) self.descrLabel.show() self.authorLabel.set_label(plugin.author) diff --git a/portato/plugin.py b/portato/plugin.py index cbadfd1..7b8a493 100644 --- a/portato/plugin.py +++ b/portato/plugin.py @@ -3,371 +3,183 @@ # File: portato/plugin.py # This file is part of the Portato-Project, a graphical portage-frontend. # -# Copyright (C) 2007 René 'Necoro' Neumann +# Copyright (C) 2008 René 'Necoro' Neumann # This is free software. You may redistribute copies of it under the terms of # the GNU General Public License version 2. # There is NO WARRANTY, to the extent permitted by law. # # Written by René 'Necoro' Neumann <necoro@necoro.net> -"""A module containing the management of the plugin system.""" - from __future__ import absolute_import -import os, os.path -from xml.dom.minidom import parse -from lxml import etree - -from .backend import system -from .constants import PLUGIN_DIR, XSD_LOCATION -from .helper import debug, info, warning, error, flatten +import os +import os.path as osp +from collections import defaultdict +from functools import wraps -class PluginImportException (ImportError): - pass +from .helper import debug, warning, info, error +from .constants import PLUGIN_DIR +from . import plugins as plugin_module -class UnmatchedDepsException (Exception): +class PluginLoadException (Exception): pass -class Options (object): - """The <options>-element.""" - - __options = ("disabled", "blocking") - - def __init__ (self, options = None): - - self.disabled = False - self.blocking = False - - if options: - self.parse(options) - - def parse (self, options): - for opt in options: - nodes = opt.childNodes - type = str(nodes[0].nodeValue.strip()) - if type in self.__options: - self.set(type, True) - - def get (self, name): - return self.__getattribute__(name) - - def set (self, name, value): - return self.__setattr__(name, value) - -class Menu: - """A single <menu>-element.""" - def __init__ (self, plugin, label, call): - """Constructor. - - @param plugin: the plugin this menu belongs to - @type plugin: Plugin - @param label: the label to show - @type label: string - @param call: the function to call relative to the import statement - @type call: string - - @raises PluginImportException: if the plugin's import could not be imported""" +class Menu (object): + __slots__ = ("label", "call") + def __init__ (self, label, call): self.label = label - self.plugin = plugin - - if self.plugin.needs_import(): # get import - imp = self.plugin.get_import() - try: - mod = __import__(imp, globals(), locals(), [call]) - except ImportError: - if self.plugin.unmatched_deps: - raise UnmatchedDepsException - else: - raise PluginImportException, imp - - try: - self.call = eval("mod."+call) # build function - except AttributeError: - raise PluginImportException, imp - else: - try: - self.call = eval(call) - except AttributeError: - raise PluginImportException, imp - -class Connect: - """A single <connect>-element.""" - - def __init__ (self, hook, type, depend_plugin): - """Constructor. - - @param hook: the parent Hook - @type hook: Hook - @param type: the type of the connect ("before", "after", "override") - @type type: string - @param depend_plugin: a plugin we are dependant on - @type depend_plugin: string or None""" - - self.type = type - self.hook = hook - self.depend_plugin = depend_plugin - - def is_before_type (self): - return self.type == "before" - - def is_after_type (self): - return self.type == "after" - - def is_override_type (self): - return self.type == "override" - -class Hook: - """A single <hook>-element.""" - - def __init__ (self, plugin, hook, call): - """Constructor. + self.call = call - @param plugin: the parent Plugin - @type plugin: Plugin - @param hook: the hook to add to - @type hook: string - @param call: the call to make - @type call: string""" +class Call (object): + __slots__ = ("plugin", "hook", "call", "type", "dep") + def __init__ (self, plugin, hook, call, type = "before", dep = None): self.plugin = plugin self.hook = hook self.call = call - self.connects = [] - - def parse_connects (self, connects): - """This gets a list of <connect>-elements and parses them. - - @param connects: the list of <connect>'s - @type connects: NodeList""" - - if not connects: # no connects - assume "before" connect - self.connects.append(Connect(self, "before", None)) - - for c in connects: - type = c.getAttribute("type") - if type == '': - type = "before" - - # get dep_plugin if available - dep_plugin = None - if c.hasChildNodes(): - nodes = c.childNodes - dep_plugin = nodes[0].nodeValue.strip() - - connect = Connect(self, type, dep_plugin) - self.connects.append(connect) - -class Plugin: - """A complete plugin.""" - - (STAT_DISABLED, STAT_TEMP_ENABLED, STAT_ENABLED, STAT_TEMP_DISABLED) = range(4) - - def __init__ (self, file, name, author): - """Constructor. - - @param file: the file name of the plugin.xml - @type file: string - @param name: the name of the plugin - @type name: Node - @param author: the author of the plugin - @type author: Node""" - - self.file = file - self.name = name.firstChild.nodeValue.strip() - self.author = author.firstChild.nodeValue.strip() - self.description = "" - self._import = None - self.hooks = [] - self.menus = [] - self.deps = [] - self.unmatched_deps = [] - self.options = Options() - - self.status = self.STAT_ENABLED - - def parse_hooks (self, hooks): - """Gets an <hooks>-elements and parses it. - - @param hooks: the hooks node - @type hooks: NodeList""" - - if hooks: - for h in hooks[0].getElementsByTagName("hook"): - hook = Hook(self, str(h.getAttribute("type")), str(h.getAttribute("call"))) - hook.parse_connects(h.getElementsByTagName("connect")) - self.hooks.append(hook) - - def parse_menus (self, menus): - """Get a list of <menu>-elements and parses them. + self.type = type + self.dep = dep - @param menus: the menu nodelist - @type menus: NodeList""" +class Hook (object): + __slots__ = ("before", "override", "after") - if menus: - for item in menus[0].getElementsByTagName("item"): - menu = Menu(self, item.firstChild.nodeValue.strip(), str(item.getAttribute("call"))) - self.menus.append(menu) + def __init__ (self): + self.before = [] + self.override = None + self.after = [] - def parse_options (self, options): - if options: - for o in options: - self.options.parse(o.getElementsByTagName("option")) +class Plugin (object): - self.status = self.STAT_DISABLED if self.options.get("disabled") else self.STAT_ENABLED + (STAT_DISABLED, STAT_TEMP_ENABLED, STAT_ENABLED, STAT_TEMP_DISABLED) = range(4) - def parse_deps (self, deps): - if deps: - for d in deps.firstChild.getElementsByTagName("dependency"): - self.deps.append(d.firstChild.nodeValue.strip()) + def __init__ (self): + self.__menus = [] + self.__calls = [] + self.status = self.STAT_ENABLED - for d in self.deps: - if not system.find_packages(d, "installed", with_version = False): - self.unmatched_deps.append(d) - - def set_import (self, imports): - """This gets a list of imports and parses them - setting the import needed to call the plugin. + @property + def author (self): + return getattr(self, "__author__", "") - @param imports: list of imports - @type imports: NodeList - - @raises PluginImportException: if the plugin's import could not be imported""" + @property + def description (self): + if hasattr(self, "__description__"): + return self.__description__ + else: + return getattr(self, "__doc__", "") - if imports: - self._import = str(imports[0].firstChild.nodeValue.strip()) + @property + def name (self): + return getattr(self, "__name__", self.__class__.__name__) - try: # try loading - mod = __import__(self._import) - del mod - except ImportError: - if self.unmatched_deps: - raise UnmatchedDepsException - else: - raise PluginImportException, self._import + @property + def menus (self): + return iter(self.__menus) - def needs_import (self): - """Returns True if an import is required prior to calling the plugin. - @rtype: bool""" - return self._import is not None + @property + def calls (self): + return iter(self.__calls) - def get_import (self): - """Returns the module to import. - @rtype: string""" - return self._import + @property + def deps (self): + if hasattr(self, "__dependency__"): + return iter(self.__dependency__) + else: + return [] - def get_option(self, name): - return self.options.get(name) + def add_menu (self, label, callable): + self.__menus.append(Menu(label, callable)) - def set_option (self, name, value): - return self.options.set(name, value) + def add_call (self, hook, callable, type = "before", dep = None): + self.__calls.append(Call(self, hook, callable, type, dep)) def is_enabled (self): return (self.status in (self.STAT_ENABLED, self.STAT_TEMP_ENABLED)) -class PluginQueue: +class PluginQueue (object): """Class managing and loading the plugins.""" - def __init__ (self, frontend, load = True): - """Constructor. - - @param frontend: the frontend used - @type frontend: string - @param load: if False nothing is loaded - @type load: bool""" + def __init__ (self): + """ + Constructor. + """ - self.frontend = frontend - self.list = [] - self.hooks = {} - if load: - self._load() + self.plugins = [] + self.hooks = defaultdict(Hook) def get_plugins (self, list_disabled = True): - return [x for x in self.list if (x.is_enabled() or list_disabled)] + return (x for x in self.plugins if (x.is_enabled() or list_disabled)) - def get_plugin_data (self, list_disabled = False): - return [(x.name, x.author) for x in self.list if (x.is_enabled() or list_disabled)] + def load (self): + """Load the plugins.""" - def get_plugin_menus (self, list_disabled = False): - return flatten([x.menus for x in self.list if (x.is_enabled() or list_disabled)]) + plugins = [] + for f in os.listdir(PLUGIN_DIR): + path = osp.join(PLUGIN_DIR, f) + if osp.isdir(path): + if osp.isfile(osp.join(path, "__init__.py")): + plugins.append(f) + else: + debug("'%s' is not a plugin: __init__.py missing", path) + else: + if f.endswith(".py"): + plugins.append(f[:-3]) + else: + debug("'%s' is not a plugin: not a .py file", path) - def hook (self, hook, *hargs, **hkwargs): - """This is a method taking care of calling the plugins. - - B{Example}:: - - @pluginQueue.hook("some_hook", data) - def function (a, b, c): - orig_call(b,c,data) - - def function (a, b, c): - hook = pluginQueue.hook("some_hook", data) - hook(orig_call)(b,c,data) + plugin_module.__path__.insert(0, PLUGIN_DIR.rstrip("/")) + plugin_module.__builtins__["Plugin"] = Plugin + plugin_module.__builtins__["register"] = register - @param hook: the name of the hook - @type hook: string""" - - def call (cmd): - """Convienience function for calling a connect. - @param cmd: the actual Connect - @type cmd: Connect""" - - imp = "" - if cmd.hook.plugin.needs_import(): # get import - imp = cmd.hook.plugin.get_import() - try: - mod = __import__(imp, globals(), locals(), [cmd.hook.call]) - except ImportError: - error(_("%s cannot be imported."), imp) - return - - try: - f = eval("mod."+cmd.hook.call) # build function - except AttributeError: - error(_("%s cannot be imported."), cmd.hook.call) - else: - try: - f = eval(cmd.hook.call) - except AttributeError: - error(_("%s cannot be imported."), cmd.hook.call) + for p in plugins: + try: + exec "from portato.plugins import %s" % p in {} + except PluginLoadException, e: + error(_("Loading plugin '%(plugin)s' failed: %(error)s"), {"plugin" : p, "error" : e.message}) - return f(*hargs, **hkwargs) # call function + self._organize() + + def add (self, plugin): + self.plugins.append(plugin) + + def hook (self, hook, *hargs, **hkwargs): def hook_decorator (func): - """This is the real decorator.""" - - if hook in self.hooks: - list = self.hooks[hook] - else: - list = ([],[],[]) + h = self.hooks[hook] + + active = Hook() # remove disabled - _list = ([],[],[]) - for i in range(len(list)): - for cmd in list[i]: - if cmd.hook.plugin.is_enabled(): - _list[i].append(cmd) - - list = _list + for type in ("before", "after"): + calls = getattr(h, type) + aCalls = getattr(active, type) + for call in calls: + if call.plugin.is_enabled(): + aCalls.append(call) + + if h.override and h.override.plugin.is_enabled(): + active.override = h.override + @wraps(func) def wrapper (*args, **kwargs): - ret = None # before - for cmd in list[0]: - debug(_("Accessing hook '%(hook)s' of plugin '%(plugin)s' (before)."), {"hook" : hook, "plugin": cmd.hook.plugin.name}) - call(cmd) + for call in active.before: + debug("Accessing hook '%(hook)s' of plugin '%(plugin)s' (before).", {"hook" : hook, "plugin": call.plugin.name}) + call.call(*hargs, **hkwargs) - if list[1]: # override - info(_("Overriding hook '%(hook)s' with plugin '%(plugin)s'."), {"hook": hook, "plugin": list[1][0].hook.plugin.name}) - ret = call(list[1][0]) + if active.override: # override + info(_("Overriding hook '%(hook)s' with plugin '%(plugin)s'."), {"hook": hook, "plugin": active.override.plugin.name}) + ret = active.override.call(*hargs, **hkwargs) else: # normal ret = func(*args, **kwargs) # after - for cmd in list[2]: - debug(_("Accessing hook '%(hook)s' of plugin '%(plugin)s' (after)."), {"hook":hook, "plugin": cmd.hook.plugin.name}) - call(cmd) + for call in active.after: + debug("Accessing hook '%(hook)s' of plugin '%(plugin)s' (after).", {"hook": hook, "plugin": call.plugin.name}) + call.call(*hargs, **hkwargs) return ret @@ -375,167 +187,101 @@ class PluginQueue: return hook_decorator - def _load (self): - """Load the plugins.""" - plugins = filter(lambda x: x.endswith(".xml"), os.listdir(PLUGIN_DIR)) - plugins = map(lambda x: os.path.join(PLUGIN_DIR, x), plugins) - schema = etree.XMLSchema(file = XSD_LOCATION) - - for p in plugins: - - try: - schema.assertValid(etree.parse(p)) - except etree.XMLSyntaxError: - error(_("Loading plugin '%s' failed. Invalid XML syntax."), p) - continue - except etree.DocumentInvalid: - error(_("Loading plugin '%s' failed. Plugin does not comply with schema."), p) - continue - - doc = parse(p) - - try: - list = doc.getElementsByTagName("plugin") - elem = list[0] - - frontendOK = None - frontends = elem.getElementsByTagName("frontends") - if frontends: - nodes = frontends[0].childNodes - for f in nodes[0].nodeValue.strip().split(): - if f == self.frontend: - frontendOK = True # one positive is enough - break - elif frontendOK is None: # do not make negative if we already have a positive - frontendOK = False - - if frontendOK is None or frontendOK == True: - plugin = Plugin(p, elem.getElementsByTagName("name")[0], elem.getElementsByTagName("author")[0]) - plugin.parse_deps(elem.getElementsByTagName("dependencies")) - plugin.parse_hooks(elem.getElementsByTagName("hooks")) - plugin.set_import(elem.getElementsByTagName("import")) - plugin.parse_menus(elem.getElementsByTagName("menu")) - plugin.parse_options(elem.getElementsByTagName("options")) - - self.list.append(plugin) - info(_("Plugin '%s' loaded."), p) - - except PluginImportException, e: - error(_("Loading plugin '%(plugin)s' failed: Could not import %(import)s"), {"plugin": p, "import": e[0]}) - finally: - doc.unlink() - - self._organize() - def _organize (self): """Creates the lists of connects in a way, that all dependencies are fullfilled.""" - unresolved_before = {} - unresolved_after = {} - star_before = {} # should be _before_ all other - star_after = {} # should be _after_ all other - - for plugin in self.list: # plugins - for hook in plugin.hooks: # hooks in plugin - if not hook.hook in self.hooks: - self.hooks[hook.hook] = ([], [], []) - - for connect in hook.connects: # connects in hook - - # type="before" - if connect.is_before_type(): - if connect.depend_plugin is None: # no dependency -> straight add - self.hooks[hook.hook][0].append(connect) - elif connect.depend_plugin == "*": - self.hooks[hook.hook][0][0:0] = [connect] - elif connect.depend_plugin == "-*": - if not hook.hook in star_before: - star_before[hook.hook] = [] - - star_before[hook.hook].append(connect) + unresolved_before = defaultdict(list) + unresolved_after = defaultdict(list) + star_before = defaultdict(Hook) # should be _before_ all other + star_after = defaultdict(Hook) # should be _after_ all other + + for plugin in self.plugins: # plugins + for call in plugin.calls: # hooks in plugin + if call.type == "before": + if call.dep is None: # no dependency -> straight add + self.hooks[call.hook].before.append(call) + elif call.dep == "*": + self.hooks[call.hook].before.insert(0, call) + elif call.dep == "-*": + star_before[call.hook].append(call) + else: + named = [x.plugin.name for x in self.hooks[call.hook].before] + if call.dep in named: + self.hooks[call.hook].before.insert(named.index(call.dep), call) else: - named = [x.plugin.name for x in self.hooks[hook.hook][0]] - if connect.depend_plugin in named: - self.hooks[hook.hook][0].insert(named.index(connect.depend_plugin), connect) - else: - if not hook.hook in unresolved_before: - unresolved_before[hook.hook] = [] - - unresolved_before[hook.hook].append(connect) - - # type = "after" - elif connect.is_after_type(): - if connect.depend_plugin is None: # no dependency -> straight add - self.hooks[hook.hook][2].append(connect) - elif connect.depend_plugin == "*": - if not hook.hook in star_after: - star_after[hook.hook] = [] - - star_after[hook.hook].append(connect) - elif connect.depend_plugin == "-*": - self.hooks[hook.hook][2][0:0] = [connect] + unresolved_before[call.hook].append(call) + + elif call.type == "after": + if call.dep is None: # no dependency -> straight add + self.hooks[call.hook].after.append(call) + elif call.dep == "*": + star_after[call.hook].append(call) + elif call.dep == "-*": + self.hooks[call.hook].after.insert(0, call) + else: + named = [x.plugin.name for x in self.hooks[call.hook].after] + if call.dep in named: + self.hooks[call.hook].after.insert(named.index(call.dep)+1, call) else: - named = [x.hook.plugin.name for x in self.hooks[hook.hook][2]] - if connect.depend_plugin in named: - self.hooks[hook.hook][2].insert(named.index(connect.depend_plugin)+1, connect) - else: - if not hook.hook in unresolved_after: - unresolved_after[hook.hook] = [] - - unresolved_after[hook.hook].append(connect) + unresolved_after[call.hook].append(call) + + # type = "override" + elif call.type == "override": + if self.hooks[call.hook].override: + warning(_("For hook '%(hook)s' an override is already defined by plugin '%(plugin)s'!"), {"hook": call.hook, "plugin": self.hooks[call.hook].override.plugin.name}) + warning(_("It is now replaced by the one from plugin '%s'!"), call.plugin.name) - # type = "override" - elif connect.is_override_type(): - if self.hooks[hook.hook][1]: - warning(_("For hook '%(hook)s' an override is already defined by plugin '%(plugin)s'!"), {"hook": hook.hook, "plugin": self.hooks[hook.hook][1][0]}) - - self.hooks[hook.hook][1][:1] = [connect] - continue + self.hooks[call.hook].override = call + continue self._resolve_unresolved(unresolved_before, unresolved_after) - for hook in star_before: - self.hooks[hook][0].extend(star_before[hook]) # append the list + for hook, calls in star_before.iteritems(): + self.hooks[hook].before.extend(calls) # append the list - for hook in star_after: - self.hooks[hook][2].extend(star_after[hook]) # append the list + for hook, calls in star_after.iteritems(): + self.hooks[hook].after.extend(calls) # append the list def _resolve_unresolved (self, before, after): - def resolve(hook, list, idx, add): + def resolve(hook, list, type, add): if not list: return - changed = False - for connect in list[:]: - named = [x.plugin.name for x in self.hooks[hook][idx]] - if connect.depend_plugin in named: - changed = True - self.hooks[hook][idx].insert(named.index(connect.depend_plugin)+add, connect) - list.remove(connect) + callList = getattr(self.hooks[hook], type) + named = [x.plugin.name for x in callList] + + while list and named: + newNamed = [] # use newNamed, so in each iteration only the plugins inserted last are searched + for call in list[:]: + if call.dep in named: + callList.insert(named.index(call.dep)+add, call) + list.remove(call) + newNamed.append(call.plugin.name) - if changed: - resolve(hook, list, idx, add) + named = newNamed for l in list: - warning("Command for hook '%(hook)s' in plugin '%(plugin)s' could not be added due to missing dependant: '%(dep)s'!", {"hook": hook, "plugin": l.hook.plugin.name, "dep": l.depend_plugin}) + warning(_("Command for hook '%(hook)s' in plugin '%(plugin)s' could not be added due to missing dependant: '%(dep)s'!"), {"hook": hook, "plugin": l.plugin.name, "dep": l.dep}) for hook in before: - resolve(hook, before[hook], 0, 0) + resolve(hook, before[hook], "before", 0) for hook in after: - resolve(hook, after[hook], 2, 1) + resolve(hook, after[hook], "after", 1) __plugins = None -def load_plugins(frontend): - """Loads the plugins for a given frontend. - @param frontend: The frontend. See L{constants.FRONTENDS} for the correct list of values. - @type frontend: string""" - +def load_plugins(): + """ + Loads the plugins. + """ + global __plugins - if __plugins is None or __plugins.frontend != frontend: - __plugins = PluginQueue(frontend) + if __plugins is None: + __plugins = PluginQueue() + __plugins.load() + def get_plugin_queue(): """Returns the actual L{PluginQueue}. If it is C{None}, they are not being loaded yet. @@ -551,3 +297,15 @@ def hook(hook, *args, **kwargs): return pseudo_decorator else: return __plugins.hook(hook, *args, **kwargs) + +def register (plugin): + if __plugins is not None: + if callable(plugin) and Plugin in plugin.__bases__: + p = plugin() # need an instance and not the class + elif isinstance(plugin, Plugin): + p = plugin + else: + raise PluginLoadException, "Is neither a subclass nor an instance of Plugin." + + info(_("Plugin '%s' loaded."), p.name) + __plugins.add(p) diff --git a/portato/plugins/__init__.py b/portato/plugins/__init__.py index fe95dbc..5080cec 100644 --- a/portato/plugins/__init__.py +++ b/portato/plugins/__init__.py @@ -3,9 +3,14 @@ # File: portato/plugins/__init__.py # This file is part of the Portato-Project, a graphical portage-frontend. # -# Copyright (C) 2007 René 'Necoro' Neumann +# Copyright (C) 2007-2008 René 'Necoro' Neumann # This is free software. You may redistribute copies of it under the terms of # the GNU General Public License version 2. # There is NO WARRANTY, to the extent permitted by law. # # Written by René 'Necoro' Neumann <necoro@necoro.net> + +""" +This is a dummy module. The plugins loaded from here are in reality stored +in /usr/share/portato/plugins or alike. +""" diff --git a/portato/plugins/etc_proposals.py b/portato/plugins/etc_proposals.py deleted file mode 100644 index d5f707f..0000000 --- a/portato/plugins/etc_proposals.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# -# File: portato/plugins/etc_proposals.py -# This file is part of the Portato-Project, a graphical portage-frontend. -# -# Copyright (C) 2007 René 'Necoro' Neumann -# This is free software. You may redistribute copies of it under the terms of -# the GNU General Public License version 2. -# There is NO WARRANTY, to the extent permitted by law. -# -# Written by René 'Necoro' Neumann <necoro@necoro.net> - -from portato.helper import error - -import os -from subprocess import Popen - -PROG=["/usr/sbin/etc-proposals"] - -def launch (options = []): - if os.getuid() == 0: - Popen(PROG+options) - else: - error("ETC_PROPOSALS :: %s",_("Cannot start etc-proposals. Not root!")) - -def etc_prop (*args, **kwargs): - """Entry point for this plugin.""" - launch(["--fastexit"]) - -def etc_prop_menu (*args, **kwargs): - launch() diff --git a/portato/plugins/notify.py b/portato/plugins/notify.py deleted file mode 100644 index 78812b3..0000000 --- a/portato/plugins/notify.py +++ /dev/null @@ -1,22 +0,0 @@ -import pynotify - -from portato import get_listener - -from portato.helper import warning, error, debug -from portato.constants import APP_ICON, APP - -def notify (retcode, **kwargs): - if retcode is None: - warning("NOTIFY :: %s", _("Notify called while process is still running!")) - else: - icon = APP_ICON - if retcode == 0: - text = _("Emerge finished!") - descr = "" - urgency = pynotify.URGENCY_NORMAL - else: - text = _("Emerge failed!") - descr = _("Error Code: %d") % retcode - urgency = pynotify.URGENCY_CRITICAL - - get_listener().send_notify(base = text, descr = descr, icon = icon, urgency = urgency) |