summaryrefslogtreecommitdiff
path: root/portato
diff options
context:
space:
mode:
Diffstat (limited to 'portato')
-rw-r--r--portato/backend/portage/__init__.py12
-rw-r--r--portato/backend/portage/package.py9
-rw-r--r--portato/backend/portage/package_22.py (renamed from portato/plugins/gpytage.py)14
-rw-r--r--portato/backend/portage/settings_22.py27
-rw-r--r--portato/backend/portage/system.py13
-rw-r--r--portato/backend/portage/system_22.py34
-rw-r--r--portato/gui/dialogs.py7
-rw-r--r--portato/gui/windows/mailinfo.py30
-rw-r--r--portato/gui/windows/main.py19
-rw-r--r--portato/gui/windows/plugin.py5
-rw-r--r--portato/plugin.py636
-rw-r--r--portato/plugins/__init__.py7
-rw-r--r--portato/plugins/etc_proposals.py31
-rw-r--r--portato/plugins/notify.py22
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)