diff options
Diffstat (limited to 'portato')
-rw-r--r-- | portato/backend/__init__.py | 11 | ||||
-rw-r--r-- | portato/backend/catapult/__init__.py | 19 | ||||
-rw-r--r-- | portato/backend/catapult/package.py | 156 | ||||
-rw-r--r-- | portato/backend/catapult/system.py | 252 | ||||
-rw-r--r-- | portato/backend/portage/system.py | 32 | ||||
-rw-r--r-- | portato/config_parser.py | 24 | ||||
-rw-r--r-- | portato/constants.py | 7 | ||||
-rw-r--r-- | portato/gui/dialogs.py | 14 | ||||
-rw-r--r-- | portato/gui/exception_handling.py | 5 | ||||
-rw-r--r-- | portato/gui/queue.py | 2 | ||||
-rw-r--r-- | portato/gui/templates/PluginWindow.glade | 180 | ||||
-rw-r--r-- | portato/gui/utils.py | 10 | ||||
-rw-r--r-- | portato/gui/windows/main.py | 22 | ||||
-rw-r--r-- | portato/gui/windows/plugin.py | 162 | ||||
-rw-r--r-- | portato/plugin.py | 825 | ||||
-rw-r--r-- | portato/plugins/__init__.py | 7 | ||||
-rw-r--r-- | portato/plugins/etc_proposals.py | 31 | ||||
-rw-r--r-- | portato/plugins/exception.py | 2 | ||||
-rw-r--r-- | portato/plugins/gpytage.py | 16 | ||||
-rw-r--r-- | portato/plugins/new_version.py | 58 | ||||
-rw-r--r-- | portato/plugins/notify.py | 22 |
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"><b>Author:</b></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) |