summaryrefslogtreecommitdiff
path: root/portato
diff options
context:
space:
mode:
Diffstat (limited to 'portato')
-rw-r--r--portato/backend/__init__.py11
-rw-r--r--portato/backend/catapult/__init__.py19
-rw-r--r--portato/backend/catapult/package.py156
-rw-r--r--portato/backend/catapult/system.py252
-rw-r--r--portato/backend/portage/system.py32
-rw-r--r--portato/config_parser.py24
-rw-r--r--portato/constants.py7
-rw-r--r--portato/gui/dialogs.py14
-rw-r--r--portato/gui/exception_handling.py5
-rw-r--r--portato/gui/queue.py2
-rw-r--r--portato/gui/templates/PluginWindow.glade180
-rw-r--r--portato/gui/utils.py10
-rw-r--r--portato/gui/windows/main.py22
-rw-r--r--portato/gui/windows/plugin.py162
-rw-r--r--portato/plugin.py825
-rw-r--r--portato/plugins/__init__.py7
-rw-r--r--portato/plugins/etc_proposals.py31
-rw-r--r--portato/plugins/exception.py2
-rw-r--r--portato/plugins/gpytage.py16
-rw-r--r--portato/plugins/new_version.py58
-rw-r--r--portato/plugins/notify.py22
21 files changed, 795 insertions, 1062 deletions
diff --git a/portato/backend/__init__.py b/portato/backend/__init__.py
index 003feb7..b2a5a43 100644
--- a/portato/backend/__init__.py
+++ b/portato/backend/__init__.py
@@ -13,14 +13,10 @@
from __future__ import absolute_import
from ..helper import debug
-from ..constants import USE_CATAPULT
from .system_interface import SystemInterface
from .exceptions import BlockedException, PackageNotFoundException, DependencyCalcError, InvalidSystemError
-if USE_CATAPULT:
- SYSTEM = "catapult"
-else:
- SYSTEM = "portage" # the name of the current system
+SYSTEM = "portage" # the name of the current system
_sys = None # the SystemInterface-instance
class _Package (object):
@@ -45,8 +41,9 @@ def set_system (new_sys):
@type new_sys: string"""
global SYSTEM
- SYSTEM = new_sys
- load_system()
+ if new_sys != SYSTEM:
+ SYSTEM = new_sys
+ load_system()
def load_system ():
"""Loads the current chosen system.
diff --git a/portato/backend/catapult/__init__.py b/portato/backend/catapult/__init__.py
deleted file mode 100644
index 348b941..0000000
--- a/portato/backend/catapult/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# File: portato/backend/catapult/__init__.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 __future__ import absolute_import
-
-from dbus.mainloop.glib import DBusGMainLoop
-DBusGMainLoop(set_as_default=True)
-
-from .system import CatapultSystem
-from .package import CatapultPackage
diff --git a/portato/backend/catapult/package.py b/portato/backend/catapult/package.py
deleted file mode 100644
index 2e4f471..0000000
--- a/portato/backend/catapult/package.py
+++ /dev/null
@@ -1,156 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# File: portato/backend/catapult/package.py
-# This file is part of the Portato-Project, a graphical portage-frontend.
-#
-# 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>
-
-from __future__ import absolute_import, with_statement
-
-from ..package import Package
-from .. import flags
-from .. import system
-from ..exceptions import BlockedException, PackageNotFoundException
-from ...helper import debug, unique_array
-
-import dbus
-import catapult
-
-import os.path
-
-class CatapultPackage(Package):
-
- bus = dbus.SessionBus()
- dbus_object = bus.get_object(catapult.get_dbus_address(catapult.DEFAULT), catapult.CATAPULT_PACKAGE_BUS, follow_name_owner_changes = True)
- proxy = dbus.Interface(dbus_object, catapult.CATAPULT_PACKAGE_IFACE)
-
- _expand = {}
-
- def _new_flags (self):
- flags = self.get_new_use_flags()
-
- nflags = []
-
- for flag in flags:
- if flag[0] == "~":
- nflags.append((flag[1:], True))
- else:
- nflags.append((flag, False))
-
- return nflags
-
- def use_expanded (self, flag, suggest = None):
-
- exp = self._expand.get(flag, False)
-
- if exp is False:
- if not suggest:
- suggest = ""
- s = str(self.proxy.use_expanded(self.get_cpv(), flag, suggest))
- if not s:
- s = None
-
- self._expand[flag] = s
- return s
- else:
- return exp
-
- def get_package_path(self):
- return str(self.proxy.get_package_path(self.get_cpv()))
-
- def is_installed(self):
- return self.proxy.is_installed(self.get_cpv())
-
- def is_overlay(self):
- return self.proxy.is_in_overlay(self.get_cpv())
-
- def get_overlay_path(self):
- return str(self.proxy.get_overlay_path(self.get_cpv()))
-
- def is_in_system (self):
- return self.proxy.is_in_system(self.get_cpv())
-
- def is_missing_keyword(self):
- return self.proxy.is_missing_keyword(self.get_cpv())
-
- def is_testing(self, use_keywords = False):
- if not use_keywords:
- return self.proxy.is_testing(self.get_cpv(), False)
- else:
- status = flags.new_testing_status(self.get_cpv())
- if status is None:
- return self.proxy.is_testing(self.get_cpv(), True)
- else:
- return status
-
- def is_masked (self, use_changed = True):
- if use_changed:
- status = flags.new_masking_status(self.get_cpv())
- if status != None: # we have locally changed it
- if status == "masked": return True
- elif status == "unmasked": return False
- else:
- error(_("BUG in flags.new_masking_status. It returns \'%s\'"), status)
- else: # we have not touched the status
- return self.proxy.is_masked(self.get_cpv())
- else: # we want the original portage value XXX: bug if masked by user AND by system
- if self.proxy.is_masked(self.get_cpv()):
- if not flags.is_locally_masked(self, changes = False): # assume that if it is locally masked, it is not masked by the system
- return True
-
- return False
-
- def get_masking_reason (self):
- return str(self.proxy.get_masking_reason(self.get_cpv()))
-
- def get_iuse_flags (self, installed = False, removeForced = True):
- return [str(x) for x in self.proxy.get_iuse_flags(self.get_cpv(), installed, removeForced)]
-
- def get_matched_dep_packages (self, depvar):
- return [str(x) for x in self.proxy.get_matched_dep_packages(self.get_cpv(), self._new_flags())]
-
- def get_dep_packages (self, depvar = ["RDEPEND", "PDEPEND", "DEPEND"], with_criterions = False):
- pkgs = self.proxy.get_dep_packages(self.get_cpv(), depvar, self._new_flags())
-
- if not with_criterions:
- return [str(x) for x,y in pkgs]
- else:
- return [(str(x),str(y)) for x,y in pkgs]
-
- def get_global_settings(self, key, installed = True):
- return str(self.proxy.get_global_settings(self.get_cpv(), key, installed))
-
- def get_ebuild_path(self):
- return str(self.proxy.get_ebuild_path(self.get_cpv()))
-
- def get_package_settings(self, var, tree = True):
- return str(self.proxy.get_package_settings(self.get_cpv(), var, tree))
-
- def get_installed_use_flags(self):
- return self.proxy.get_installed_use_flags(self.get_cpv())
-
- def get_actual_use_flags(self):
- return self.proxy.get_actual_use_flags(self.get_cpv(), self._new_flags())
-
- def compare_version(self, other):
- return self.proxy.compare_version(self.get_cpv(), other.get_cpv())
-
- def matches (self, criterion):
- return self.proxy.matches(self.get_cpv(), criterion)
-
- def get_files (self):
- return self.proxy.get_files(self.get_cpv())
-
- def get_name(self):
- return str(self.proxy.get_name(self.get_cpv()))
-
- def get_version(self):
- return str(self.proxy.get_version(self.get_cpv()))
-
- def get_category(self):
- return str(self.proxy.get_category(self.get_cpv()))
diff --git a/portato/backend/catapult/system.py b/portato/backend/catapult/system.py
deleted file mode 100644
index 3a3bac5..0000000
--- a/portato/backend/catapult/system.py
+++ /dev/null
@@ -1,252 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# File: portato/backend/catapult/system.py
-# This file is part of the Portato-Project, a graphical portage-frontend.
-#
-# 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>
-
-from __future__ import absolute_import
-
-import re, os
-from threading import Event
-import dbus
-import catapult
-
-from .package import CatapultPackage
-from ..system_interface import SystemInterface
-from ...helper import debug, info, warning, unique_array
-
-class CatapultSystem (SystemInterface):
-
- def __init__ (self):
- SystemInterface.__init__(self)
-
- self.bus = dbus.SessionBus()
- # get the system
- so = self.bus.get_object(catapult.get_dbus_address(catapult.DEFAULT), catapult.CATAPULT_SYSTEM_BUS, follow_name_owner_changes = True)
- self.proxy = dbus.Interface(so, catapult.CATAPULT_SYSTEM_IFACE)
-
- def get_version (self):
- admint = dbus.Interface(self.bus.get_object(catapult.get_dbus_address(catapult.DEFAULT), catapult.CATAPULT_BUS), catapult.CATAPULT_ADMIN_IFACE)
- return "Catapult: %s v. %s" % (self.proxy.bus_name.split(".")[-1], str(admint.version()))
-
- def geneticize_list (self, list_of_packages, only_cpv = False):
- """Convertes a list of cpv's into L{backend.Package}s.
-
- @param list_of_packages: the list of packages
- @type list_of_packages: string[]
- @param only_cpv: do nothing - return the passed list
- @type only_cpv: boolean
- @returns: converted list
- @rtype: PortagePackage[]
- """
-
- if not only_cpv:
- return [CatapultPackage(x) for x in list_of_packages]
- else:
- return [str(x) for x in list_of_packages]
-
-
- def split_cpv (self, cpv):
- split = self.proxy.split_cpv(cpv)
- if all(split):
- return map(str, split)
- else:
- return None
-
- def cpv_matches (self, cpv, criterion):
- return CatapultPackage(cpv).matches(criterion)
-
- def find_best(self, list, only_cpv = False):
- if only_cpv:
- return str(self.proxy.find_best(list))
- else:
- return CatapultPackage(self.proxy.find_best(list))
-
- def find_best_match (self, search_key, masked = False, only_installed = False, only_cpv = False):
- p = self.proxy.find_best_match(search_key, masked, only_installed)
-
- if p :
- if not only_cpv:
- return CatapultPackage(p)
- else:
- return str(p)
- return None
-
- def _wrap_find(self, key, masked, set, withVersion, only_cpv):
-
- l = []
- try:
- l = self.proxy.find_packages(key, set, masked, withVersion)
- except dbus.DBusException, e:
- name, data = str(e).split("\n")[-2].split(": ")[1:]
-
- if name == catapult.CATAPULT_ERR_AMBIGUOUS_PACKAGE:
- debug("Ambigous packages: %s.", data)
- l = []
- for cp in data.split(","):
- l += self.proxy.find_packages(cp, set, masked, withVersion)
- else:
- raise
-
- return self.geneticize_list(l, not(withVersion) or only_cpv)
-
- def find_packages (self, search_key, masked = False, only_cpv = False):
- return self._wrap_find(search_key, masked, "all", True, only_cpv)
-
- def find_installed_packages (self, search_key, masked = False, only_cpv = False):
- return self._wrap_find(search_key, masked, "installed", True, only_cpv)
-
- def find_system_packages (self, only_cpv = False):
-# result = self.proxy.find_system_packages()
-# if only_cpv:
-# return result
-# else:
-# return tuple(map(self.geneticize_list, result))
- return (self._wrap_find(search_key, False, "system", True, only_cpv), [])
-
- def find_world_packages (self, only_cpv = False):
-# result = self.proxy.find_world_packages()
-# if only_cpv:
-# return result
-# else:
-# return tuple(map(self.geneticize_list, result))
- return (self._wrap_find(search_key, False, "world", True, only_cpv), [])
-
- def _wrap_find_all (self, key, masked, set, withVersion, only_cpv):
- if not key:
- key = ""
- else:
- key = "*%s*" % key
-
- l = self.proxy.find_packages("", set, masked, withVersion)
-
- if key:
- l = catapult.filter_list(key, l)
-
- return self.geneticize_list(l, not(withVersion) or only_cpv)
-
- def find_all_installed_packages (self, name = None, withVersion = True, only_cpv = False):
- return self._wrap_find_all(name, True, "installed", withVersion, only_cpv)
-
- def find_all_uninstalled_packages (self, name = None, only_cpv = False):
- return self._wrap_find_all(name, True, "uninstalled", True, only_cpv)
-
- def find_all_packages (self, name = None, withVersion = True, only_cpv = False):
- return self._wrap_find_all(name, True, "all", withVersion, only_cpv)
-
- def find_all_world_packages (self, name = None, only_cpv = False):
- return self._wrap_find_all(name, True, "world", withVersion, only_cpv)
-
- def find_all_system_packages (self, name = None, only_cpv = False):
- return self._wrap_find_all(name, True, "system", withVersion, only_cpv)
-
- def list_categories (self, name = None):
- cats = self.proxy.list_categories()
- if name:
- cats = catapult.filter_list("*%s*" % name, cats)
-
- return map(str, cats)
-
- def sort_package_list(self, pkglist):
- return self.geneticize_list(self.proxy.sort_package_list([x.get_cpv() for x in pkglist]))
-
- def reload_settings (self):
- return self.proxy.reload_settings()
-
- def update_world (self, newuse = False, deep = False):
-
- ret = []
- e = Event()
-
- def wait (list):
- ret.extend([(CatapultPackage(x), CatapultPackage(y)) for x,y in list])
- e.set()
-
- def error (ex):
- e.set()
- raise ex
-
- self.proxy.update_world(newuse, deep, {}, reply_handler = wait, error_handler = error, timeout = 300)
- e.wait()
- return ret
- # return [(CatapultPackage(x), CatapultPackage(y)) for x,y in self.proxy.update_world(newuse, deep, {}, timeout = 300)]
-
- def get_updated_packages (self):
- ret = []
- e = Event()
-
- def wait (list):
- ret.extend([CatapultPackage(x) for x in list])
- e.set()
-
- def error (ex):
- e.set()
- raise ex
-
- self.proxy.get_updated_packages(reply_handler = wait, error_handler = error, timeout = 300)
- e.wait()
- return ret
-
- def get_use_desc (self, flag, package = None):
- if not package:
- package = ""
- return str(self.proxy.get_use_desc(flag, package))
-
- def get_global_settings(self, key):
- return str(self.proxy.get_global_settings(key))
-
- def new_package (self, cpv):
- return CatapultPackage(cpv)
-
- def get_config_path (self):
- return str(self.proxy.get_config_path())
-
- def get_sync_command (self):
- return [str(x) for x in self.proxy.get_sync_command()]
-
- def get_merge_command (self):
- return [str(x) for x in self.proxy.get_merge_command()]
-
- def get_oneshot_option (self):
- return [str(x) for x in self.proxy.get_oneshot_option()]
-
- def get_newuse_option (self):
- return [str(x) for x in self.proxy.get_newuse_option()]
-
- def get_deep_option (self):
- return [str(x) for x in self.proxy.get_deep_option()]
-
- def get_update_option (self):
- return [str(x) for x in self.proxy.get_update_option()]
-
- def get_pretend_option (self):
- return [str(x) for x in self.proxy.get_pretend_option()]
-
- def get_unmerge_option (self):
- return [str(x) for x in self.proxy.get_unmerge_option()]
-
- def get_environment (self):
- default_opts = self.get_global_settings("EMERGE_DEFAULT_OPTS")
- opts = dict(os.environ)
-
- if default_opts:
- opt_list = default_opts.split()
- changed = False
-
- for option in ["--ask", "-a", "--pretend", "-p"]:
- if option in opt_list:
- opt_list.remove(option)
- changed = True
-
- if changed:
- opts.update(EMERGE_DEFAULT_OPTS = " ".join(opt_list))
-
- opts.update(TERM = "xterm")
-
- return opts
diff --git a/portato/backend/portage/system.py b/portato/backend/portage/system.py
index c241a4b..4453df7 100644
--- a/portato/backend/portage/system.py
+++ b/portato/backend/portage/system.py
@@ -194,8 +194,8 @@ class PortageSystem (SystemInterface):
if VERSION >= (2,1,5):
t += [pkg.get_cpv() for pkg in self.find_packages(search_key, self.SET_INSTALLED) if not (pkg.is_testing(True) or pkg.is_masked())]
- else:
- t = self.find_packages(search_key, self.SET_INSTALLED, only_cpv=True)
+ elif not only_installed: # no need to run twice
+ t += self.find_packages(search_key, self.SET_INSTALLED, only_cpv=True)
if t:
t = unique_array(t)
@@ -237,6 +237,19 @@ class PortageSystem (SystemInterface):
"""
new_packages = []
+
+ def append(crit, best, inst):
+ if not best:
+ return
+
+ if not best.is_installed() and (best.is_masked() or best.is_testing(True)): # check to not update unnecessairily
+ for i in inst:
+ if i.matches(crit):
+ debug("The installed %s matches %s. Discarding upgrade to masked version.", i.get_cpv(), crit)
+ return
+
+ new_packages.append(best)
+
for p in packages:
inst = self.find_packages(p, self.SET_INSTALLED)
@@ -256,10 +269,10 @@ class PortageSystem (SystemInterface):
myslots.add(best_p.get_package_settings("SLOT")) # add the slot of the best package in portage
for slot in myslots:
- new_packages.append(\
- self.find_best(self.find_packages("%s:%s" % (i.get_cp(), slot), only_cpv = True)))
+ crit = "%s:%s" % (p, slot)
+ append(crit, self.find_best_match(crit), inst)
else:
- new_packages.append(best_p)
+ append(p, best_p, inst)
return new_packages
@@ -348,15 +361,6 @@ class PortageSystem (SystemInterface):
else:
for pkg in bm:
if not pkg: continue
- if not pkg.is_installed() and (pkg.is_masked() or pkg.is_testing(True)): # check to not update unnecessairily
- cont = False
- for inst in self.find_packages(pkg.get_cp(), self.SET_INSTALLED, only_cpv = True):
- if self.cpv_matches(inst, i):
- debug("The installed %s matches %s. Discarding upgrade to masked version.", inst, i)
- cont = True
- break
- if cont: continue
-
check(pkg, state[1], appended) # XXX: should be 'or'ed with prev_appended?
for p in self.get_new_packages(packages):
diff --git a/portato/config_parser.py b/portato/config_parser.py
index 1383d69..6515d1b 100644
--- a/portato/config_parser.py
+++ b/portato/config_parser.py
@@ -285,8 +285,8 @@ class ConfigParser:
:Exceptions:
- KeyNotFoundException : Raised if the specified key could not be found.
- SectionNotFoundException : Raised if the specified section could not be found.
+ - `KeyNotFoundException` : Raised if the specified key could not be found.
+ - `SectionNotFoundException` : Raised if the specified section could not be found.
"""
try:
@@ -315,8 +315,8 @@ class ConfigParser:
:Exceptions:
- KeyNotFoundException : Raised if the specified key could not be found.
- SectionNotFoundException : Raised if the specified section could not be found.
+ - `KeyNotFoundException` : Raised if the specified key could not be found.
+ - `SectionNotFoundException` : Raised if the specified section could not be found.
"""
section = section.upper()
@@ -339,9 +339,9 @@ class ConfigParser:
:Exceptions:
- KeyNotFoundException : Raised if the specified key could not be found.
- SectionNotFoundException : Raised if the specified section could not be found.
- ValueError : Raised if the key accessed is not a boolean.
+ - `KeyNotFoundException` : Raised if the specified key could not be found.
+ - `SectionNotFoundException` : Raised if the specified section could not be found.
+ - `ValueError` : Raised if the key accessed is not a boolean.
"""
section = section.upper()
@@ -369,8 +369,8 @@ class ConfigParser:
:Exceptions:
- KeyNotFoundException : Raised if the specified key could not be found.
- SectionNotFoundException : Raised if the specified section could not be found.
+ - `KeyNotFoundException` : Raised if the specified key could not be found.
+ - `SectionNotFoundException` : Raised if the specified section could not be found.
"""
section = section.upper()
@@ -394,9 +394,9 @@ class ConfigParser:
:Exceptions:
- KeyNotFoundException : Raised if the specified key could not be found.
- SectionNotFoundException : Raised if the specified section could not be found.
- ValueError : if the old/new value is not a boolean
+ - `KeyNotFoundException` : Raised if the specified key could not be found.
+ - `SectionNotFoundException` : Raised if the specified section could not be found.
+ - `ValueError` : if the old/new value is not a boolean
"""
section = section.upper()
diff --git a/portato/constants.py b/portato/constants.py
index 32f0f9b..3d7217f 100644
--- a/portato/constants.py
+++ b/portato/constants.py
@@ -22,8 +22,6 @@ These should be set during the installation.
@type HOME: string
@var SU_COMMAND: command to execute to "su"
@type SU_COMMAND: string
-@var USE_CATAPULT: use the catapult backend or the normal ones
-@type USE_CATAPULT: boolean
@var CONFIG_DIR: The configuration directory.
@type CONFIG_DIR: string
@@ -45,8 +43,6 @@ These should be set during the installation.
@type SETTINGS_DIR: string
@var TEMPLATE_DIR: Directory containing the UI template files.
@type TEMPLATE_DIR: string
-@var XSD_LOCATION: Path of the plugin schema.
-@type XSD_LOCATION: string
"""
import os
from os.path import join as pjoin
@@ -60,7 +56,6 @@ APP = "portato"
VERSION = "9999"
HOME = os.environ["HOME"]
SU_COMMAND = "gksu -D 'Portato'"
-USE_CATAPULT = False
# config
CONFIG_DIR = "/etc/portato/"
@@ -73,5 +68,3 @@ LOCALE_DIR = "i18n/"
PLUGIN_DIR = pjoin(DATA_DIR, "plugins/")
SETTINGS_DIR = pjoin(HOME, "."+APP)
TEMPLATE_DIR = "portato/gui/templates/"
-
-XSD_LOCATION = pjoin(DATA_DIR, "plugin.xsd")
diff --git a/portato/gui/dialogs.py b/portato/gui/dialogs.py
index 7f8a736..bf7acc7 100644
--- a/portato/gui/dialogs.py
+++ b/portato/gui/dialogs.py
@@ -21,8 +21,8 @@ def mail_failure_dialog(e):
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)
+ dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_NONE, _("Do you really want to quit?"))
+ dialog.format_secondary_text(_("There are some packages in the emerge queue and/or an emerge process is running."))
dialog.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)
ret = dialog.run()
dialog.destroy()
@@ -40,7 +40,8 @@ def io_ex_dialog (io_ex):
return ret
def blocked_dialog (blocked, blocks):
- dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("%(blocked)s is blocked by %(blocks)s.\nPlease unmerge the blocking package.") % {"blocked":blocked, "blocks" : blocks})
+ dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("%(blocked)s is blocked by %(blocks)s.") % {"blocked":blocked, "blocks" : blocks})
+ dialog.format_secondary_text(_("Please unmerge the blocking package."))
ret = dialog.run()
dialog.destroy()
return ret
@@ -52,7 +53,8 @@ def not_root_dialog ():
return ret
def unmask_dialog (cpv):
- dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, _("%s seems to be masked.\nDo you want to unmask it and its dependencies?") % cpv)
+ dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, _("%s seems to be masked.") % cpv )
+ dialog.format_secondary_text(_("Do you want to unmask it and its dependencies?"))
ret = dialog.run()
dialog.destroy()
return ret
@@ -65,8 +67,8 @@ def nothing_found_dialog ():
def changed_flags_dialog (what = "flags"):
check = gtk.CheckButton(_("Do not show this dialog again."))
- hintMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK,
- _("You have changed %s.\nPortato will write these changes into the appropriate files.\nPlease backup them if you think it is necessairy.") % what)
+ hintMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("Changed %s") % what)
+ hintMB.format_secondary_text(_("Portato will write these changes into the appropriate files.\nPlease backup them if you think it is necessairy."))
hintMB.vbox.add(check)
hintMB.vbox.show_all()
ret = hintMB.run()
diff --git a/portato/gui/exception_handling.py b/portato/gui/exception_handling.py
index eadf124..dae95ed 100644
--- a/portato/gui/exception_handling.py
+++ b/portato/gui/exception_handling.py
@@ -100,16 +100,13 @@ def convert (version):
def get_version_infos():
from ..constants import VERSION
from ..backend import system
- from lxml import etree
return "\n".join((
"Portato version: %s" % VERSION,
"Python version: %s" % sys.version,
"Used backend: %s" % system.get_version(),
"pygtk: %s (using GTK+: %s)" % (convert(gtk.pygtk_version), convert(gtk.gtk_version)),
- "pygobject: %s (using GLib: %s)" % (convert(gobject.pygobject_version), convert(gobject.glib_version)),
- "lxml: %s" % convert(etree.LXML_VERSION),
- ""))
+ "pygobject: %s (using GLib: %s)" % (convert(gobject.pygobject_version), convert(gobject.glib_version))))
def get_trace(type, value, tb):
trace = StringIO()
diff --git a/portato/gui/queue.py b/portato/gui/queue.py
index cb5b334..c04d449 100644
--- a/portato/gui/queue.py
+++ b/portato/gui/queue.py
@@ -248,9 +248,9 @@ class EmergeQueue:
self.update_tree(parentIt, cpv, unmask, oneshot = oneshot, type = type)
else: # not update
if type == "install":
- self._queue_append(cpv, oneshot)
if self.tree:
self.update_tree(self.tree.get_emerge_it(), cpv, unmask, type = type, oneshot = oneshot)
+ self._queue_append(cpv, oneshot)
elif type == "update" and self.tree:
self.update_tree(self.tree.get_update_it(), cpv, unmask, type = type, oneshot = oneshot)
diff --git a/portato/gui/templates/PluginWindow.glade b/portato/gui/templates/PluginWindow.glade
index 1de5fc0..f76193e 100644
--- a/portato/gui/templates/PluginWindow.glade
+++ b/portato/gui/templates/PluginWindow.glade
@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
-<!--Generated with glade3 3.4.1 on Fri Feb 29 00:03:30 2008 -->
+<!--Generated with glade3 3.4.5 on Fri Jul 4 15:24:27 2008 -->
<glade-interface>
<widget class="GtkWindow" id="PluginWindow">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Plugins</property>
+ <property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
<property name="destroy_with_parent">True</property>
@@ -26,6 +27,7 @@
<child>
<widget class="GtkTreeView" id="pluginList">
<property name="visible">True</property>
+ <property name="headers_visible">False</property>
<property name="rules_hint">True</property>
<property name="enable_search">False</property>
</widget>
@@ -33,6 +35,180 @@
</widget>
</child>
<child>
+ <widget class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <property name="row_spacing">10</property>
+ <child>
+ <widget class="GtkButton" id="installBtn">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">_Install dependencies</property>
+ <property name="use_underline">True</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="cb_install_clicked"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ <property name="x_padding">10</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkExpander" id="depExpander">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <widget class="GtkTreeView" id="depList">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="headers_clickable">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Needed dependencies</property>
+ <property name="single_line_mode">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_padding">10</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="descrLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">label</property>
+ </widget>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ <property name="y_padding">10</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Author:&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ <property name="single_line_mode">True</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="authorLabel">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">label</property>
+ <property name="single_line_mode">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHButtonBox" id="buttonBox">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <property name="homogeneous">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_EDGE</property>
+ <child>
+ <widget class="GtkRadioButton" id="enabledRB">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Enabled</property>
+ <property name="response_id">0</property>
+ <property name="active">True</property>
+ <signal name="toggled" handler="cb_state_toggled"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkRadioButton" id="tempEnabledRB">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Temporarily enabled</property>
+ <property name="response_id">0</property>
+ <property name="group">enabledRB</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkRadioButton" id="tempDisabledRB">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Temporarily disabled</property>
+ <property name="response_id">0</property>
+ <property name="group">enabledRB</property>
+ <signal name="toggled" handler="cb_state_toggled"/>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkRadioButton" id="disabledRB">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Disabled</property>
+ <property name="response_id">0</property>
+ <property name="group">enabledRB</property>
+ <signal name="toggled" handler="cb_state_toggled"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <placeholder/>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
<widget class="GtkHButtonBox" id="hbuttonbox2">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@@ -68,7 +244,7 @@
</widget>
<packing>
<property name="expand">False</property>
- <property name="position">1</property>
+ <property name="position">2</property>
</packing>
</child>
</widget>
diff --git a/portato/gui/utils.py b/portato/gui/utils.py
index a4e1e6e..923f2fa 100644
--- a/portato/gui/utils.py
+++ b/portato/gui/utils.py
@@ -24,7 +24,7 @@ from functools import wraps
# some backend things
from ..backend import flags, system, set_system
from ..helper import debug, info, set_log_level
-from ..constants import USE_CATAPULT, APP, LOCALE_DIR
+from ..constants import APP, LOCALE_DIR
# parser
from ..config_parser import ConfigParser
@@ -86,8 +86,7 @@ class Config (ConfigParser):
def modify_system_config (self):
"""Sets the system config.
@see: L{backend.set_system()}"""
- if not USE_CATAPULT:
- set_system(self.get("system"))
+ set_system(self.get("system"))
def modify_external_configs (self):
"""Convenience function setting all external configs."""
@@ -132,7 +131,8 @@ class Config (ConfigParser):
ConfigParser.write(self)
self.modify_external_configs()
-class PkgData:
+class PkgData (object):
+ __slots__ = ("cat", "pkg", "inst")
def __init__ (self, cat, pkg, inst):
self.cat = cat
@@ -146,7 +146,7 @@ class PkgData:
return cmp(self.pkg.lower(), other.pkg.lower())
def __repr__ (self):
- return "<Package (%(cat)s, %(pkg)s, %(inst)s)>" % self.__dict__
+ return "<Package (%(cat)s, %(pkg)s, %(inst)s)>" % {"cat" : self.cat, "pkg" : self.pkg, "inst" : self.inst}
class Database (object):
"""An internal database which holds a simple dictionary cat -> [package_list]."""
diff --git a/portato/gui/windows/main.py b/portato/gui/windows/main.py
index 188a5e1..50dd366 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,11 @@ 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:
+ if p.status == p.STAT_HARD_DISABLED:
return ""
+
+ return int(p.status >= p.STAT_ENABLED)
+
return _save
# SESSION VERSION
@@ -1559,9 +1557,9 @@ class MainWindow (Window):
if queue is None:
plugins = []
else:
- plugins = queue.get_plugins()
-
- PluginWindow(self.window, plugins)
+ plugins = list(queue.get_plugins())
+
+ PluginWindow(self.window, plugins, self.queue)
return True
def cb_show_updates_clicked (self, *args):
diff --git a/portato/gui/windows/plugin.py b/portato/gui/windows/plugin.py
index fb9446e..a0694be 100644
--- a/portato/gui/windows/plugin.py
+++ b/portato/gui/windows/plugin.py
@@ -15,6 +15,9 @@ from __future__ import absolute_import
import gtk
from .basic import AbstractDialog
+from ..dialogs import blocked_dialog, unmask_dialog
+from ...backend import system
+from ...backend.exceptions import PackageNotFoundException, BlockedException
from ...helper import debug
class PluginWindow (AbstractDialog):
@@ -24,7 +27,7 @@ class PluginWindow (AbstractDialog):
for s in (_("Disabled"), _("Temporarily enabled"), _("Enabled"), _("Temporarily disabled")):
statsStore.append([s])
- def __init__ (self, parent, plugins):
+ def __init__ (self, parent, plugins, queue = None):
"""Constructor.
@param parent: the parent window
@@ -32,50 +35,77 @@ class PluginWindow (AbstractDialog):
AbstractDialog.__init__(self, parent)
self.plugins = plugins
+ self.queue = queue
self.changedPlugins = {}
+ self.inst = []
+ self.ninst = []
- view = self.tree.get_widget("pluginList")
- self.store = gtk.ListStore(str,str,str)
+ self.buttons = map(self.tree.get_widget, ("disabledRB", "tempEnabledRB", "enabledRB", "tempDisabledRB"))
+ map(lambda b: b.set_mode(False), self.buttons)
+
+ self.descrLabel = self.tree.get_widget("descrLabel")
+ self.authorLabel = self.tree.get_widget("authorLabel")
+
+ self.depExpander = self.tree.get_widget("depExpander")
+ self.installBtn = self.tree.get_widget("installBtn")
+ self.depList = self.tree.get_widget("depList")
+ self.build_dep_list()
+
+ self.buttonBox = self.tree.get_widget("buttonBox")
+
+ self.instIcon = self.window.render_icon(gtk.STOCK_YES, gtk.ICON_SIZE_MENU)
- view.set_model(self.store)
+ self.view = self.tree.get_widget("pluginList")
+ self.store = gtk.ListStore(str)
- cell = gtk.CellRendererText()
- col = gtk.TreeViewColumn(_("Plugin"), cell, markup = 0)
- view.append_column(col)
+ self.view.set_model(self.store)
- col = gtk.TreeViewColumn(_("Authors"), cell, text = 1)
- view.append_column(col)
-
- ccell = gtk.CellRendererCombo()
- ccell.set_property("model", self.statsStore)
- ccell.set_property("text-column", 0)
- ccell.set_property("has-entry", False)
- ccell.set_property("editable", True)
- ccell.connect("edited", self.cb_status_changed)
- col = gtk.TreeViewColumn(_("Status"), ccell, markup = 2)
- view.append_column(col)
+ cell = gtk.CellRendererText()
+ col = gtk.TreeViewColumn("Plugin", cell, markup = 0)
+ self.view.append_column(col)
- for p in (("<b>"+p.name+"</b>", p.author, _(self.statsStore[p.status][0])) for p in plugins):
- self.store.append(p)
+ for p in plugins:
+ self.store.append(["<b>%s</b>" % p.name])
+
+ self.view.get_selection().connect("changed", self.cb_list_selection)
self.window.show_all()
- def cb_status_changed (self, cell, path, new_text):
- path = int(path)
-
- self.store[path][2] = "<b>%s</b>" % new_text
+ def build_dep_list (self):
+ store = gtk.ListStore(gtk.gdk.Pixbuf, str)
+
+ self.depList.set_model(store)
+
+ col = gtk.TreeViewColumn()
+
+ cell = gtk.CellRendererPixbuf()
+ col.pack_start(cell, False)
+ col.add_attribute(cell, "pixbuf", 0)
+
+ cell = gtk.CellRendererText()
+ col.pack_start(cell, True)
+ col.add_attribute(cell, "text", 1)
+
+ self.depList.append_column(col)
+
+ def fill_dep_list (self, inst = [], ninst = []):
+ store = self.depList.get_model()
+ store.clear()
+
+ for dep in inst:
+ store.append([self.instIcon, dep])
+ for dep in ninst:
+ store.append([None, dep])
- # convert string to constant
- const = None
- for num, val in enumerate(self.statsStore):
- if val[0] == new_text:
- const = num
- break
+ def cb_state_toggled (self, rb):
+
+ plugin = self.get_actual()
- assert (const is not None)
+ if plugin:
+ state = self.buttons.index(rb)
- self.changedPlugins.update({self.plugins[path] : const})
- debug("new changed plugins: %s => %d", self.plugins[path].name, const)
+ self.changedPlugins[plugin] = state
+ debug("new changed plugins: %s => %d", plugin.name, state)
def cb_ok_clicked (self, btn):
for plugin, val in self.changedPlugins.iteritems():
@@ -83,3 +113,69 @@ class PluginWindow (AbstractDialog):
self.close()
return True
+
+ def cb_list_selection (self, selection):
+ plugin = self.get_actual()
+ self.inst = []
+ self.ninst = []
+
+ if plugin:
+ if not plugin.description:
+ self.descrLabel.hide()
+ else:
+ self.descrLabel.set_markup(plugin.description)
+ self.descrLabel.show()
+
+ self.authorLabel.set_label(plugin.author)
+
+ status = self.changedPlugins.get(plugin, plugin.status)
+ self.buttons[status].set_active(True)
+
+ if plugin.deps:
+
+ for dep in plugin.deps:
+ if system.find_packages(dep, pkgSet = system.SET_INSTALLED, with_version = False):
+ self.inst.append(dep)
+ else:
+ self.ninst.append(dep)
+
+ self.fill_dep_list(self.inst, self.ninst)
+ self.depExpander.show()
+
+ self.installBtn.show()
+ self.installBtn.set_sensitive(bool(self.ninst))
+
+ else:
+ self.installBtn.hide()
+ self.depExpander.hide()
+
+ self.buttonBox.set_sensitive(not plugin._unresolved_deps and plugin.status != plugin.STAT_HARD_DISABLED)
+
+ def cb_install_clicked (self, *args):
+ if not self.queue:
+ return False
+
+ for cpv in self.ninst:
+
+ pkg = system.find_best_match(cpv, masked = False, only_cpv = True)
+ if not pkg:
+ pkg = system.find_best_match(cpv, masked = True, only_cpv = True)
+
+ try:
+ try:
+ self.queue.append(pkg, type = "install")
+ except PackageNotFoundException, e:
+ if unmask_dialog(e[0]) == gtk.RESPONSE_YES:
+ self.queue.append(pkg, type = "install", unmask = True)
+ except BlockedException, e:
+ blocked_dialog(e[0], e[1])
+
+ return True
+
+ def get_actual (self):
+ store, it = self.view.get_selection().get_selected()
+
+ if it:
+ return self.plugins[int(store.get_path(it)[0])]
+ else:
+ return None
diff --git a/portato/plugin.py b/portato/plugin.py
index 5926922..0bb161c 100644
--- a/portato/plugin.py
+++ b/portato/plugin.py
@@ -3,349 +3,421 @@
# 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."""
+"""
+A module managing the plugins for Portato.
+"""
from __future__ import absolute_import
-
-import os, os.path
-from xml.dom.minidom import parse
-from lxml import etree
-
-from .constants import PLUGIN_DIR, XSD_LOCATION
-from .helper import debug, info, warning, error, flatten
-
-class PluginImportException (ImportError):
+__docformat__ = "restructuredtext"
+
+import os
+import os.path as osp
+import traceback
+from collections import defaultdict
+from functools import wraps
+
+from .helper import debug, warning, info, error
+from .constants import PLUGIN_DIR
+from .backend import system
+from . import plugins as plugin_module
+
+class PluginLoadException (Exception):
+ """
+ Exception signaling a failed plugin loading.
+ """
pass
-class Options (object):
- """The <options>-element."""
+class Menu (object):
+ """
+ One single menu entry.
- __options = ("disabled", "blocking")
+ :IVariables:
- def __init__ (self, options = None):
+ label : string
+ The label of the entry. Can have underscores to define the shortcut.
- self.disabled = False
- self.blocking = False
+ call
+ The function to call, if the entry is clicked.
+ """
+ __slots__ = ("label", "call")
- 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 __init__ (self, label, call):
+ self.label = label
+ self.call = call
- def get (self, name):
- return self.__getattribute__(name)
+class Call (object):
+ """
+ This class represents an object, which is attached to a specified hook.
- def set (self, name, value):
- return self.__setattr__(name, value)
+ :IVariables:
-class Menu:
- """A single <menu>-element."""
- def __init__ (self, plugin, label, call):
- """Constructor.
+ plugin : `Plugin`
+ The plugin where this call belongs to.
- @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
+ hook : string
+ The name of the corresponding hook.
- @raises PluginImportException: if the plugin's import could not be imported"""
+ call
+ The function to call.
- self.label = label
- self.plugin = plugin
+ type : string
+ This is either ``before``, ``after`` or ``override`` and defines the type of the call:
- if self.plugin.needs_import(): # get import
- imp = self.plugin.get_import()
- try:
- mod = __import__(imp, globals(), locals(), [call])
- except ImportError:
- raise PluginImportException, imp
+ before
+ access before the original function
+ override
+ access *instead of* the original function. **USE THIS ONLY IF YOU KNOW WHAT YOU ARE DOING**
+ after
+ access after the original function has been called
- try:
- self.call = eval("mod."+call) # build function
- except AttributeError:
- raise PluginImportException, imp
- else:
- try:
- self.call = eval(call)
- except AttributeError:
- raise PluginImportException, imp
+ Default: ``before``
-class Connect:
- """A single <connect>-element."""
+ dep : string
+ This defines a plugin which should be executed after/before this one.
+ ``"*"`` means all and ``"-*"`` means none.
+ """
+ __slots__ = ("plugin", "hook", "call", "type", "dep")
- def __init__ (self, hook, type, depend_plugin):
- """Constructor.
+ def __init__ (self, plugin, hook, call, type = "before", dep = None):
+ self.plugin = plugin
+ self.hook = hook
+ self.call = call
+ self.type = type
+ self.dep = dep
- @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"""
+class Hook (object):
+ """
+ Representing a hook with all the `Call` s for the different types.
+ """
+
+ __slots__ = ("before", "override", "after")
- self.type = type
- self.hook = hook
- self.depend_plugin = depend_plugin
+ def __init__ (self):
+ self.before = []
+ self.override = None
+ self.after = []
- def is_before_type (self):
- return self.type == "before"
+class Plugin (object):
+ """
+ This is the main plugin object. It is used where ever a plugin is wanted, and it is the one, which needs to be subclassed by plugin authors.
- def is_after_type (self):
- return self.type == "after"
+ :CVariables:
- def is_override_type (self):
- return self.type == "override"
+ STAT_DISABLED : status
+ Status: Disabled.
-class Hook:
- """A single <hook>-element."""
+ STAT_TEMP_ENABLED : status
+ Status: Enabled for this session only.
- def __init__ (self, plugin, hook, call):
- """Constructor.
+ STAT_ENABLED : status
+ Status: Enabled.
- @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"""
+ STAT_TEMP_DISABLED : status
+ Status: Disabled for this session only.
- self.plugin = plugin
- self.hook = hook
- self.call = call
- self.connects = []
+ STAT_HARD_DISABLED : status
+ Status: Forced disabled by program (i.e. because of errors in the plugin).
+ """
- 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))
+ (STAT_DISABLED, STAT_TEMP_ENABLED, STAT_ENABLED, STAT_TEMP_DISABLED) = range(4)
+ STAT_HARD_DISABLED = -1
+
+ def __init__ (self, disable = False):
+ """
+ :param disable: Forcefully disable the plugin
+ :type disable: bool
+ """
+ self.__menus = [] #: List of `Menu`
+ self.__calls = [] #: List of `Call`
+ self._unresolved_deps = False #: Does this plugin has unresolved dependencies?
+
+ self.status = self.STAT_ENABLED #: The status of this plugin
- 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)
+ if disable:
+ self.status = self.STAT_HARD_DISABLED
+
+ def _init (self):
+ """
+ Method called from outside to init the extension parts of this plugin.
+ If the current status is `STAT_HARD_DISABLED` or there are unresolved dependencies, the init process is not started.
+ """
+
+ for d in self.deps:
+ if not system.find_packages(d, pkgSet=system.SET_INSTALLED, with_version = False):
+ self._unresolved_deps = True
+ break
-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"""
+ if self.status != self.STAT_HARD_DISABLED and not self._unresolved_deps:
+ self.init()
+
+ def init (self):
+ """
+ This method is called by `_init` and should be overriden by the plugin author.
+
+ :precond: No unresolved deps and the status is not `STAT_HARD_DISABLED`.
+ """
+ pass
+
+ @property
+ def author (self):
+ """
+ Returns the plugin's author.
+ The author is given by the ``__author__`` variable.
+
+ :rtype: string
+ """
+ return getattr(self, "__author__", "")
+
+ @property
+ def description (self):
+ """
+ Returns the description of this plugin.
+ It is given by either a ``__description__`` variable or by the normal class docstring.
+
+ :rtype: string
+ """
+ if hasattr(self, "__description__"):
+ return self.__description__
+ else:
+ doc = getattr(self, "__doc__", "")
- self.file = file
- self.name = name.firstChild.nodeValue.strip()
- self.author = author.firstChild.nodeValue.strip()
- self._import = None
- self.hooks = []
- self.menus = []
- self.options = Options()
+ if not doc or doc == Plugin.__doc__:
+ return ""
+ else:
+ return doc
+
+ @property
+ def name (self):
+ """
+ The name of the plugin. If no ``__name__`` variable is given, the class name is taken.
+
+ :rtype: string
+ """
+ return getattr(self, "__name__", self.__class__.__name__)
+
+ @property
+ def menus (self):
+ """
+ Returns an iterator over the menus for this plugin.
+
+ :rtype: iter<`Menu`>
+ """
+ return iter(self.__menus)
+
+ @property
+ def calls (self):
+ """
+ Returns an iterator over the registered calls for this plugin.
+
+ :rtype: iter<`Call`>
+ """
+ return iter(self.__calls)
+
+ @property
+ def deps (self):
+ """
+ Returns an iterator of the dependencies or ``[]`` if there are none.
+ The dependencies are given in the ``__dependency__`` variable.
+
+ :rtype: [] or iter<string>
+ """
+ if hasattr(self, "__dependency__"):
+ return iter(self.__dependency__)
+ else:
+ return []
- self.status = self.STAT_ENABLED
+ @property
+ def enabled (self):
+ """
+ Returns ``True`` if the plugin is enabled.
- def parse_hooks (self, hooks):
- """Gets an <hooks>-elements and parses it.
+ :rtype: boolean
+ :see: `status`
+ """
+ return (self.status in (self.STAT_ENABLED, self.STAT_TEMP_ENABLED))
+
+ def add_menu (self, label, callable):
+ """
+ Adds a new menu item for this plugin.
- @param hooks: the hooks node
- @type hooks: NodeList"""
+ :see: `Menu`
+ """
+ self.__menus.append(Menu(label, callable))
- 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 add_call (self, hook, callable, type = "before", dep = None):
+ """
+ Adds a new call for this plugin.
- def parse_menus (self, menus):
- """Get a list of <menu>-elements and parses them.
+ :see: `Call`
+ """
+ self.__calls.append(Call(self, hook, callable, type, dep))
- @param menus: the menu nodelist
- @type menus: NodeList"""
- if menus:
- for item in menus[0].getElementsByTagName("item"):
- menu = Menu(self, item.firstChild.nodeValue.strip(), str(item.getAttribute("call")))
- self.menus.append(menu)
+class PluginQueue (object):
+ """
+ Class managing and loading the plugins.
+
+ :IVariables:
- def parse_options (self, options):
- if options:
- for o in options:
- self.options.parse(o.getElementsByTagName("option"))
+ plugins : `Plugin` []
+ The list of managed plugins.
- self.status = self.STAT_DISABLED if self.options.get("disabled") else self.STAT_ENABLED
-
- def set_import (self, imports):
- """This gets a list of imports and parses them - setting the import needed to call the plugin.
+ hooks : string -> `Hook`
+ For each hook name map to a `Hook` object holding the corresponding `Call` objects.
+ """
- @param imports: list of imports
- @type imports: NodeList
-
- @raises PluginImportException: if the plugin's import could not be imported"""
+ def __init__ (self):
+ """
+ Constructor.
+ """
- if imports:
- self._import = str(imports[0].firstChild.nodeValue.strip())
+ self.plugins = []
+ self.hooks = defaultdict(Hook)
- try: # try loading
- mod = __import__(self._import)
- del mod
- except ImportError:
- raise PluginImportException, self._import
+ def get_plugins (self, list_disabled = True):
+ """
+ Returns the plugins.
- def needs_import (self):
- """Returns True if an import is required prior to calling the plugin.
- @rtype: bool"""
- return self._import is not None
+ :param list_disabled: Also list disabled plugins.
+ :type list_disabled: boolean
- def get_import (self):
- """Returns the module to import.
- @rtype: string"""
- return self._import
+ :rtype: iter<`Plugin`>
+ """
+ return (x for x in self.plugins if (x.enabled or list_disabled))
- def get_option(self, name):
- return self.options.get(name)
+ def load (self):
+ """
+ Load the plugins.
+
+ This method scans the `portato.constants.PLUGIN_DIR` for python modules and tries to load them. If the modules are real plugins,
+ they have called `register` and thus the plugins are added.
+ """
+
+ # look them up
+ 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])
+ elif f.endswith(".pyc") or f.endswith(".pyo"):
+ pass # ignore .pyc and .pyo
+ else:
+ debug("'%s' is not a plugin: not a .py file", path)
+
+ # some magic ...
+ plugin_module.__path__.insert(0, PLUGIN_DIR.rstrip("/")) # make the plugins loadable as "portato.plugins.name"
+ # add Plugin and register to the builtins, so the plugins always have the correct version :)
+ plugin_module.__builtins__["Plugin"] = Plugin
+ plugin_module.__builtins__["register"] = register
+
+ for p in plugins: # import them
+ 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})
+ except:
+ tb = traceback.format_exc()
+ error(_("Loading plugin '%(plugin)s' failed: %(error)s"), {"plugin" : p, "error" : tb})
- def set_option (self, name, value):
- return self.options.set(name, value)
+ self._organize()
- def is_enabled (self):
- return (self.status in (self.STAT_ENABLED, self.STAT_TEMP_ENABLED))
+ def add (self, plugin, disable = False):
+ """
+ Adds a plugin to the internal list.
-class PluginQueue:
- """Class managing and loading the plugins."""
+ :Parameters:
- 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"""
+ plugin : `Plugin`
+ ``Plugin`` subclass or instance to add. If a class is passed, it is instantiated.
- self.frontend = frontend
- self.list = []
- self.hooks = {}
- if load:
- self._load()
+ disable : boolean
+ Disable the plugin.
- def get_plugins (self, list_disabled = True):
- return [x for x in self.list if (x.is_enabled() or list_disabled)]
+ :raise PluginLoadException: passed plugin is not of class `Plugin`
+ """
- 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)]
+ if callable(plugin) and Plugin in plugin.__bases__:
+ p = plugin(disable = disable) # need an instance and not the class
+ elif isinstance(plugin, Plugin):
+ p = plugin
+ if disable:
+ p.status = p.STAT_HARD_DISABLED
+ else:
+ raise PluginLoadException, "Is neither a subclass nor an instance of Plugin."
- def get_plugin_menus (self, list_disabled = False):
- return flatten([x.menus for x in self.list if (x.is_enabled() or list_disabled)])
+ p._init()
- def hook (self, hook, *hargs, **hkwargs):
- """This is a method taking care of calling the plugins.
+ self.plugins.append(p)
- 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)
+ if p.status == p.STAT_HARD_DISABLED:
+ msg = _("Plugin is disabled!")
+ elif p._unresolved_deps:
+ msg = _("Plugin has unresolved dependencies - disabled!")
+ else:
+ msg = ""
+
+ info("%s %s", _("Plugin '%s' loaded.") % p.name, msg)
- @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)
+ def hook (self, hook, *hargs, **hkwargs):
+ """
+ The decorator to use in the program.
+ All parameters except ``hook`` are passed to plugins.
- return f(*hargs, **hkwargs) # call function
+ :param hook: the name of the hook
+ :type hook: string
+ """
def hook_decorator (func):
- """This is the real decorator."""
-
- if hook in self.hooks:
- list = self.hooks[hook]
- else:
- list = ([],[],[])
+ """
+ The real decorator.
+ """
+ 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.enabled:
+ aCalls.append(call)
+
+ if h.override and h.override.plugin.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
@@ -353,178 +425,127 @@ 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_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)
+ """
+ Organizes the lists of `Call` in a way, that all dependencies are fullfilled.
+ """
+ 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]
- if changed:
- resolve(hook, list, idx, add)
+ 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)
+
+ 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.
+ """
+ Returns the actual `PluginQueue`. If it is ``None``, they are not being loaded yet.
- @returns: the actual L{PluginQueue} or C{None}
- @rtype: PluginQueue"""
+ :rtype: `PluginQueue` or ``None``"""
return __plugins
def hook(hook, *args, **kwargs):
+ """
+ Shortcut to `PluginQueue.hook`. If no `PluginQueue` is loaded, this does nothing.
+ """
if __plugins is None:
def pseudo_decorator(f):
return f
return pseudo_decorator
else:
return __plugins.hook(hook, *args, **kwargs)
+
+def register (plugin, disable = False):
+ """
+ Registers a plugin.
+
+ :see: `PluginQueue.add`
+ """
+ if __plugins is not None:
+ __plugins.add(plugin, disable)
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/exception.py b/portato/plugins/exception.py
deleted file mode 100644
index 64bdb77..0000000
--- a/portato/plugins/exception.py
+++ /dev/null
@@ -1,2 +0,0 @@
-def throw (*args, **kwargs):
- raise Exception, "As requested, Sir!"
diff --git a/portato/plugins/gpytage.py b/portato/plugins/gpytage.py
deleted file mode 100644
index 22cc7ef..0000000
--- a/portato/plugins/gpytage.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# File: portato/plugins/gpytage.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 subprocess import Popen
-
-def gpytage(*args, **kwargs):
- Popen(["/usr/bin/gpytage"])
diff --git a/portato/plugins/new_version.py b/portato/plugins/new_version.py
deleted file mode 100644
index fe69292..0000000
--- a/portato/plugins/new_version.py
+++ /dev/null
@@ -1,58 +0,0 @@
-try:
- from bzrlib import plugin, branch
-except ImportError:
- plugin = branch = None
-import gobject
-
-from portato.helper import debug, warning
-from portato import get_listener
-from portato.constants import VERSION, APP_ICON, APP
-from portato.gui.utils import GtkThread
-
-def find_version (rev):
- try:
- b = branch.Branch.open("lp:portato")
- except Exception, e:
- warning("NEW_VERSION :: Exception occured while accessing the remote branch: %s", str(e))
- return
-
- debug("NEW_VERSION :: Installed rev: %s - Current rev: %s", rev, b.revno())
- if int(rev) < int(b.revno()):
- def callback():
- get_listener().send_notify(base = "New Portato Live Version Found", descr = "You have rev. %s, but the most recent revision is %s." % (rev, b.revno()), icon = APP_ICON)
- return False
-
- gobject.idle_add(callback)
-
-def start_thread(rev):
- t = GtkThread(target = find_version, name = "Version Updater Thread", args = (rev,))
- t.setDaemon(True)
- t.start()
- return True
-
-def run_menu (*args, **kwargs):
- """
- Run the thread once.
- """
- if not all((plugin, branch)):
- return None
-
- v = VERSION.split()
- if len(v) != 3 or v[0] != "9999":
- return None
-
- rev = v[-1]
-
- plugin.load_plugins() # to have lp: addresses parsed
-
- start_thread(rev)
- return rev
-
-def run (*args, **kwargs):
- """
- Run the thread once and add a 30 minutes timer.
- """
- rev = run_menu()
-
- if rev is not None:
- gobject.timeout_add(30*60*1000, start_thread, rev) # call it every 30 minutes
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)