From afa1de13f0576ace6dcbb0176490fd20922950cd Mon Sep 17 00:00:00 2001 From: René 'Necoro' Neumann Date: Tue, 2 Sep 2008 13:01:17 +0200 Subject: Switch from tabs to 4 spaces --- plugins/etc_proposals.py | 42 +- plugins/exception.py | 2 +- plugins/gpytage.py | 14 +- plugins/new_version.py | 94 +- plugins/notify.py | 48 +- plugins/reload_portage.py | 18 +- portato.py | 118 +- portato/TEST_helper.py | 36 +- portato/__init__.py | 62 +- portato/backend/__init__.py | 64 +- portato/backend/exceptions.py | 16 +- portato/backend/flags.py | 1308 ++++++------ portato/backend/package.py | 786 +++---- portato/backend/portage/__init__.py | 8 +- portato/backend/portage/package.py | 572 +++--- portato/backend/portage/package_22.py | 10 +- portato/backend/portage/sets.py | 168 +- portato/backend/portage/settings.py | 78 +- portato/backend/portage/settings_22.py | 12 +- portato/backend/portage/system.py | 768 +++---- portato/backend/portage/system_22.py | 60 +- portato/backend/system_interface.py | 492 ++--- portato/config_parser.py | 958 ++++----- portato/dependency.py | 260 +-- portato/gui/__init__.py | 18 +- portato/gui/dialogs.py | 134 +- portato/gui/exception_handling.py | 196 +- portato/gui/queue.py | 1234 +++++------ portato/gui/session.py | 16 +- portato/gui/updater.py | 200 +- portato/gui/utils.py | 530 ++--- portato/gui/views.py | 214 +- portato/gui/windows/about.py | 16 +- portato/gui/windows/basic.py | 180 +- portato/gui/windows/mailinfo.py | 106 +- portato/gui/windows/main.py | 3482 ++++++++++++++++---------------- portato/gui/windows/plugin.py | 316 +-- portato/gui/windows/preference.py | 384 ++-- portato/gui/windows/search.py | 98 +- portato/gui/windows/splash.py | 60 +- portato/gui/windows/update.py | 188 +- portato/gui/wrapper.py | 622 +++--- portato/helper.py | 214 +- portato/plistener.py | 222 +- portato/plugin.py | 966 ++++----- portato/session.py | 202 +- portato/waiting_queue.py | 88 +- setup.py | 28 +- 48 files changed, 7854 insertions(+), 7854 deletions(-) diff --git a/plugins/etc_proposals.py b/plugins/etc_proposals.py index c32c8f3..5b4f67a 100644 --- a/plugins/etc_proposals.py +++ b/plugins/etc_proposals.py @@ -16,26 +16,26 @@ import os from subprocess import Popen class EtcProposals (Plugin): - __author__ = "René 'Necoro' Neumann" - __description__ = "Adds support for etc-proposals, a graphical etc-update replacement." - __dependency__ = ["app-portage/etc-proposals"] - - def init (self): - self.prog = ["/usr/sbin/etc-proposals"] - self.add_call("after_emerge", self.hook, type = "after") - self.add_menu("Et_c-Proposals", self.menu) - - def launch (self, options = []): - if os.getuid() == 0: - Popen(self.prog+options) - else: - error("ETC_PROPOSALS :: %s",_("Cannot start etc-proposals. Not root!")) - - def hook (self, *args, **kwargs): - """Entry point for this plugin.""" - self.launch(["--fastexit"]) - - def menu (self, *args): - self.launch() + __author__ = "René 'Necoro' Neumann" + __description__ = "Adds support for etc-proposals, a graphical etc-update replacement." + __dependency__ = ["app-portage/etc-proposals"] + + def init (self): + self.prog = ["/usr/sbin/etc-proposals"] + self.add_call("after_emerge", self.hook, type = "after") + self.add_menu("Et_c-Proposals", self.menu) + + def launch (self, options = []): + if os.getuid() == 0: + Popen(self.prog+options) + else: + error("ETC_PROPOSALS :: %s",_("Cannot start etc-proposals. Not root!")) + + def hook (self, *args, **kwargs): + """Entry point for this plugin.""" + self.launch(["--fastexit"]) + + def menu (self, *args): + self.launch() register(EtcProposals) diff --git a/plugins/exception.py b/plugins/exception.py index e653853..ff5a8e9 100644 --- a/plugins/exception.py +++ b/plugins/exception.py @@ -11,7 +11,7 @@ # Written by René 'Necoro' Neumann def throw (*args, **kwargs): - raise Exception, "As requested, Sir!" + raise Exception, "As requested, Sir!" p = Plugin() p.__name__ = "ExceptionThrower" diff --git a/plugins/gpytage.py b/plugins/gpytage.py index d8c2831..5ebd6b4 100644 --- a/plugins/gpytage.py +++ b/plugins/gpytage.py @@ -13,14 +13,14 @@ from subprocess import Popen class GPytage (Plugin): - __author__ = "René 'Necoro' Neumann" - __description__ = "Adds a menu entry to directly start gpytage, a config editor." - __dependency__ = ["app-portage/gpytage"] + __author__ = "René 'Necoro' Neumann" + __description__ = "Adds a menu entry to directly start gpytage, a config editor." + __dependency__ = ["app-portage/gpytage"] - def init (self): - self.add_menu("Config _Editor", self.menu) + def init (self): + self.add_menu("Config _Editor", self.menu) - def menu (self, *args): - Popen(["/usr/bin/gpytage"]) + def menu (self, *args): + Popen(["/usr/bin/gpytage"]) register(GPytage) diff --git a/plugins/new_version.py b/plugins/new_version.py index f3479b4..5391507 100644 --- a/plugins/new_version.py +++ b/plugins/new_version.py @@ -11,9 +11,9 @@ # Written by René 'Necoro' Neumann try: - from bzrlib import plugin, branch + from bzrlib import plugin, branch except ImportError: - plugin = branch = None + plugin = branch = None import gobject from portato.helper import debug, warning @@ -22,59 +22,59 @@ from portato.constants import VERSION, APP_ICON, APP from portato.gui.utils import GtkThread class NewVersionFinder(Plugin): - """ - Checks for a new version of portato every 30 minutes and on startup. - """ - __author__ = "René 'Necoro' Neumann" - __dependency__ = ["dev-util/bzr"] + """ + Checks for a new version of portato every 30 minutes and on startup. + """ + __author__ = "René 'Necoro' Neumann" + __dependency__ = ["dev-util/bzr"] - def init (self): - self.add_call("main", self.run) - self.add_menu("Check for new _versions", self.menu) + def init (self): + self.add_call("main", self.run) + self.add_menu("Check for new _versions", self.menu) - def find_version (self, 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 + def find_version (self, 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) + 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(self, rev): - t = GtkThread(target = self.find_version, name = "Version Updater Thread", args = (rev,)) - t.setDaemon(True) - t.start() - return True + def start_thread(self, rev): + t = GtkThread(target = self.find_version, name = "Version Updater Thread", args = (rev,)) + t.setDaemon(True) + t.start() + return True - def menu (self, *args, **kwargs): - """ - Run the thread once. - """ - v = VERSION.split() - if len(v) != 3 or v[0] != "9999": - return None + def menu (self, *args, **kwargs): + """ + Run the thread once. + """ + v = VERSION.split() + if len(v) != 3 or v[0] != "9999": + return None - rev = v[-1] + rev = v[-1] - plugin.load_plugins() # to have lp: addresses parsed - - self.start_thread(rev) - return rev + plugin.load_plugins() # to have lp: addresses parsed + + self.start_thread(rev) + return rev - def run (self, *args, **kwargs): - """ - Run the thread once and add a 30 minutes timer. - """ - rev = self.menu() + def run (self, *args, **kwargs): + """ + Run the thread once and add a 30 minutes timer. + """ + rev = self.menu() - if rev is not None: - gobject.timeout_add(30*60*1000, self.start_thread, rev) # call it every 30 minutes + if rev is not None: + gobject.timeout_add(30*60*1000, self.start_thread, rev) # call it every 30 minutes register(NewVersionFinder, (branch is None)) diff --git a/plugins/notify.py b/plugins/notify.py index 6446812..7a3776a 100644 --- a/plugins/notify.py +++ b/plugins/notify.py @@ -13,9 +13,9 @@ disable = False try: - import pynotify + import pynotify except ImportError: - disable = True + disable = True from portato import get_listener @@ -23,27 +23,27 @@ from portato.helper import warning, error, debug from portato.constants import APP_ICON, APP class Notify (Plugin): - __author__ = "René 'Necoro' Neumann" - __description__ = "Show notifications when an emerge process finishes." - __dependency__ = ["dev-python/notify-python"] - - def init (self): - self.add_call("after_emerge", self.notify) - - def notify (self, 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) + __author__ = "René 'Necoro' Neumann" + __description__ = "Show notifications when an emerge process finishes." + __dependency__ = ["dev-python/notify-python"] + + def init (self): + self.add_call("after_emerge", self.notify) + + def notify (self, 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) register(Notify, disable) diff --git a/plugins/reload_portage.py b/plugins/reload_portage.py index 280bd92..1aea01f 100644 --- a/plugins/reload_portage.py +++ b/plugins/reload_portage.py @@ -13,15 +13,15 @@ from portato.backend import system class ReloadPortage (Plugin): - __author__ = "René 'Necoro' Neumann" - __description__ = """Reloads portage when an emerge process has finished. + __author__ = "René 'Necoro' Neumann" + __description__ = """Reloads portage when an emerge process has finished. This can take some time, but sometimes it is necessairy.""" - - def init(self): - self.add_call("after_emerge", self.hook, type = "after", dep = "EtcProposals") - self.status = self.STAT_DISABLED # disable by default - - def hook (self, *args, **kwargs): - system.reload_settings() + + def init(self): + self.add_call("after_emerge", self.hook, type = "after", dep = "EtcProposals") + self.status = self.STAT_DISABLED # disable by default + + def hook (self, *args, **kwargs): + system.reload_settings() register(ReloadPortage) diff --git a/portato.py b/portato.py index cc973be..c2b861e 100755 --- a/portato.py +++ b/portato.py @@ -25,73 +25,73 @@ from portato.helper import debug, info from portato.constants import VERSION, LOCALE_DIR, APP, SU_COMMAND def main (): - # set gettext stuff - locale.setlocale(locale.LC_ALL, '') - gettext.install(APP, LOCALE_DIR, unicode = True) + # set gettext stuff + locale.setlocale(locale.LC_ALL, '') + gettext.install(APP, LOCALE_DIR, unicode = True) - # build the parser - desc = "Portato - A Portage GUI." - usage = "%prog [options] [frontend]" - vers = "%%prog v. %s" % VERSION + # build the parser + desc = "Portato - A Portage GUI." + usage = "%prog [options] [frontend]" + vers = "%%prog v. %s" % VERSION - parser = OptionParser(version = vers, prog = "Portato", description = desc, usage = usage) - - parser.add_option("--shm", action = "store", nargs = 3, type="long", dest = "shm", - help = SUPPRESS_HELP) + parser = OptionParser(version = vers, prog = "Portato", description = desc, usage = usage) + + parser.add_option("--shm", action = "store", nargs = 3, type="long", dest = "shm", + help = SUPPRESS_HELP) - parser.add_option("-F", "--no-fork", "-L", action = "store_true", dest = "nofork", default = False, - help = _("do not fork off as root") + (" (%s)" % _("-L is deprecated"))) + parser.add_option("-F", "--no-fork", "-L", action = "store_true", dest = "nofork", default = False, + help = _("do not fork off as root") + (" (%s)" % _("-L is deprecated"))) - # run parser - (options, args) = parser.parse_args() + # run parser + (options, args) = parser.parse_args() - # close listener at exit - atexit.register(get_listener().close) + # close listener at exit + atexit.register(get_listener().close) - if options.nofork or os.getuid() == 0: # start GUI - from portato.gui import run - info("%s v. %s", _("Starting Portato"), VERSION) - - if options.shm: - get_listener().set_send(*options.shm) - else: - get_listener().set_send() - - try: - run() - except KeyboardInterrupt: - debug("Got KeyboardInterrupt.") - - else: # start us again in root modus and launch listener - - import shm_wrapper as shm + if options.nofork or os.getuid() == 0: # start GUI + from portato.gui import run + info("%s v. %s", _("Starting Portato"), VERSION) + + if options.shm: + get_listener().set_send(*options.shm) + else: + get_listener().set_send() + + try: + run() + except KeyboardInterrupt: + debug("Got KeyboardInterrupt.") + + else: # start us again in root modus and launch listener + + import shm_wrapper as shm - mem = shm.create_memory(1024, permissions=0600) - sig = shm.create_semaphore(InitialValue = 0, permissions = 0600) - rw = shm.create_semaphore(InitialValue = 1, permissions = 0600) - - # start listener - lt = threading.Thread(target=get_listener().set_recv, args = (mem, sig, rw)) - lt.setDaemon(False) - lt.start() - - # set DBUS_SESSION_BUS_ADDRESS to "" to make dbus work as root ;) - env = os.environ.copy() - env.update(DBUS_SESSION_BUS_ADDRESS="") - cmd = SU_COMMAND.split() - - sp = subprocess.Popen(cmd+["%s --no-fork --shm %ld %ld %ld" % (sys.argv[0], mem.key, sig.key, rw.key)], env = env) + mem = shm.create_memory(1024, permissions=0600) + sig = shm.create_semaphore(InitialValue = 0, permissions = 0600) + rw = shm.create_semaphore(InitialValue = 1, permissions = 0600) + + # start listener + lt = threading.Thread(target=get_listener().set_recv, args = (mem, sig, rw)) + lt.setDaemon(False) + lt.start() + + # set DBUS_SESSION_BUS_ADDRESS to "" to make dbus work as root ;) + env = os.environ.copy() + env.update(DBUS_SESSION_BUS_ADDRESS="") + cmd = SU_COMMAND.split() + + sp = subprocess.Popen(cmd+["%s --no-fork --shm %ld %ld %ld" % (sys.argv[0], mem.key, sig.key, rw.key)], env = env) - # wait for process to finish - try: - sp.wait() - debug("Subprocess finished") - except KeyboardInterrupt: - debug("Got KeyboardInterrupt.") + # wait for process to finish + try: + sp.wait() + debug("Subprocess finished") + except KeyboardInterrupt: + debug("Got KeyboardInterrupt.") - if lt.isAlive(): - debug("Listener is still running. Close it.") - get_listener().close() + if lt.isAlive(): + debug("Listener is still running. Close it.") + get_listener().close() if __name__ == "__main__": - main() + main() diff --git a/portato/TEST_helper.py b/portato/TEST_helper.py index cce0b61..f0b069b 100644 --- a/portato/TEST_helper.py +++ b/portato/TEST_helper.py @@ -5,28 +5,28 @@ import helper class HelperTest (unittest.TestCase): - def testFlatten(self): - list = [[1,2],[3,4],[[5],[6,7,8], 9]] - flist = helper.flatten(list) - self.assertEqual(flist, [1,2,3,4,5,6,7,8,9], "List not flattend correctly.") + def testFlatten(self): + list = [[1,2],[3,4],[[5],[6,7,8], 9]] + flist = helper.flatten(list) + self.assertEqual(flist, [1,2,3,4,5,6,7,8,9], "List not flattend correctly.") - def testUniqueArray(self): + def testUniqueArray(self): - def equal (l1, l2): - for i in l1: - if i not in l2: - return False - l2.remove(i) - return True + def equal (l1, l2): + for i in l1: + if i not in l2: + return False + l2.remove(i) + return True - list1 = [1,4,5,2,1,7,9,11,2,4,7,12] - result1 = [1,4,5,2,7,9,11,12] + list1 = [1,4,5,2,1,7,9,11,2,4,7,12] + result1 = [1,4,5,2,7,9,11,12] - list2 = [[x] for x in list1] - result2 = [[x] for x in result1] + list2 = [[x] for x in list1] + result2 = [[x] for x in result1] - self.assert_(equal(helper.unique_array(list1), result1), "Make hashable list unique does not work.") - self.assert_(equal(helper.unique_array(list2), result2), "Make unhashable list unique does not work.") + self.assert_(equal(helper.unique_array(list1), result1), "Make hashable list unique does not work.") + self.assert_(equal(helper.unique_array(list2), result2), "Make unhashable list unique does not work.") if __name__ == "__main__": - unittest.main() + unittest.main() diff --git a/portato/__init__.py b/portato/__init__.py index 4b6a808..b5a9859 100644 --- a/portato/__init__.py +++ b/portato/__init__.py @@ -18,36 +18,36 @@ import os class OutputFormatter (logging.Formatter): - colors = { - "blue" : 34, - "green" : 32, - "red" : 31, - "yellow": 33 - } + colors = { + "blue" : 34, + "green" : 32, + "red" : 31, + "yellow": 33 + } - def __init__(self, *args, **kwargs): - logging.Formatter.__init__(self, *args, **kwargs) + def __init__(self, *args, **kwargs): + logging.Formatter.__init__(self, *args, **kwargs) - for key, value in self.colors.iteritems(): - self.colors[key] = "\x1b[01;%02dm*\x1b[39;49;00m" % value + for key, value in self.colors.iteritems(): + self.colors[key] = "\x1b[01;%02dm*\x1b[39;49;00m" % value - def format (self, record): - string = logging.Formatter.format(self, record) - color = None + def format (self, record): + string = logging.Formatter.format(self, record) + color = None - if os.isatty(sys.stderr.fileno()): - if record.levelno <= logging.DEBUG: - color = self.colors["blue"] - elif record.levelno <= logging.INFO: - color = self.colors["green"] - elif record.levelno <= logging.WARNING: - color = self.colors["yellow"] - else: - color = self.colors["red"] - else: - color = "%s:" % record.levelname + if os.isatty(sys.stderr.fileno()): + if record.levelno <= logging.DEBUG: + color = self.colors["blue"] + elif record.levelno <= logging.INFO: + color = self.colors["green"] + elif record.levelno <= logging.WARNING: + color = self.colors["yellow"] + else: + color = self.colors["red"] + else: + color = "%s:" % record.levelname - return "%s %s" % (color, string) + return "%s %s" % (color, string) # set the whole logging stuff formatter = OutputFormatter("%(message)s (%(filename)s:%(lineno)s)") @@ -61,9 +61,9 @@ logging.getLogger("portatoLogger").propagate = False __listener = None def get_listener(): - global __listener - if __listener is None: - from .plistener import PListener - __listener = PListener() - - return __listener + global __listener + if __listener is None: + from .plistener import PListener + __listener = PListener() + + return __listener diff --git a/portato/backend/__init__.py b/portato/backend/__init__.py index b2a5a43..1f62d6b 100644 --- a/portato/backend/__init__.py +++ b/portato/backend/__init__.py @@ -20,52 +20,52 @@ SYSTEM = "portage" # the name of the current system _sys = None # the SystemInterface-instance class _Package (object): - """Wrapping class from which L{portato.backend.Package} inherits. This is used by the flags module to check - whether an object is a package. It cannot use the normal Package class as this results in cyclic dependencies.""" + """Wrapping class from which L{portato.backend.Package} inherits. This is used by the flags module to check + whether an object is a package. It cannot use the normal Package class as this results in cyclic dependencies.""" - def __init__ (self): - raise TypeError, "Calling __init__ on portato.backend._Package objects is not allowed." + def __init__ (self): + raise TypeError, "Calling __init__ on portato.backend._Package objects is not allowed." class SystemWrapper (SystemInterface): - """This is a wrapper to the different system interfaces, allowing the direct import via C{from portato.backend import system}. - With this wrapper a change of the system is propagated to all imports.""" - - def __getattribute__ (self, name): - """Just pass all attribute accesses directly to _sys.""" - return getattr(_sys, name) + """This is a wrapper to the different system interfaces, allowing the direct import via C{from portato.backend import system}. + With this wrapper a change of the system is propagated to all imports.""" + + def __getattribute__ (self, name): + """Just pass all attribute accesses directly to _sys.""" + return getattr(_sys, name) def set_system (new_sys): - """Sets the current system to a new one. + """Sets the current system to a new one. - @param new_sys: the name of the system to take - @type new_sys: string""" + @param new_sys: the name of the system to take + @type new_sys: string""" - global SYSTEM - if new_sys != SYSTEM: - SYSTEM = new_sys - load_system() + global SYSTEM + if new_sys != SYSTEM: + SYSTEM = new_sys + load_system() def load_system (): - """Loads the current chosen system. + """Loads the current chosen system. - @raises InvalidSystemError: if an inappropriate system is set""" - - global _sys + @raises InvalidSystemError: if an inappropriate system is set""" + + global _sys - if SYSTEM == "portage": - debug("Setting Portage System") - from .portage import PortageSystem - _sys = PortageSystem () - elif SYSTEM == "catapult": - debug("Setting Catapult System") - from .catapult import CatapultSystem - _sys = CatapultSystem() - else: - raise InvalidSystemError, SYSTEM + if SYSTEM == "portage": + debug("Setting Portage System") + from .portage import PortageSystem + _sys = PortageSystem () + elif SYSTEM == "catapult": + debug("Setting Catapult System") + from .catapult import CatapultSystem + _sys = CatapultSystem() + else: + raise InvalidSystemError, SYSTEM system = SystemWrapper() def is_package(what): - return isinstance(what, _Package) + return isinstance(what, _Package) load_system() diff --git a/portato/backend/exceptions.py b/portato/backend/exceptions.py index 42f9d44..37e9bda 100644 --- a/portato/backend/exceptions.py +++ b/portato/backend/exceptions.py @@ -11,17 +11,17 @@ # Written by René 'Necoro' Neumann class BlockedException (Exception): - """An exception marking, that some package is blocking another one.""" - pass + """An exception marking, that some package is blocking another one.""" + pass class PackageNotFoundException (Exception): - """An exception marking that a package could not be found.""" - pass + """An exception marking that a package could not be found.""" + pass class DependencyCalcError (Exception): - """An error occured during dependency calculation.""" - pass + """An error occured during dependency calculation.""" + pass class InvalidSystemError (Exception): - """An invalid system is set.""" - pass + """An invalid system is set.""" + pass diff --git a/portato/backend/flags.py b/portato/backend/flags.py index b151665..4aa6b8e 100644 --- a/portato/backend/flags.py +++ b/portato/backend/flags.py @@ -20,738 +20,738 @@ from . import system, is_package from ..helper import debug, error, unique_array CONFIG = { - "usefile" : "portato", - "maskfile" : "portato", - "testingfile" : "portato", - "usePerVersion" : True, - "maskPerVersion" : True, - "testingPerVersion" : True - } + "usefile" : "portato", + "maskfile" : "portato", + "testingfile" : "portato", + "usePerVersion" : True, + "maskPerVersion" : True, + "testingPerVersion" : True + } class Constants: - def __init__ (self): - self.clear() - - def clear (self): - self._use_path = None - self._mask_path = None - self._unmask_path = None - self._testing_path = None - self._use_path_is_dir = None - self._mask_path_is_dir = None - self._unmask_path_is_dir = None - self._testing_path_is_dir = None + def __init__ (self): + self.clear() + + def clear (self): + self._use_path = None + self._mask_path = None + self._unmask_path = None + self._testing_path = None + self._use_path_is_dir = None + self._mask_path_is_dir = None + self._unmask_path_is_dir = None + self._testing_path_is_dir = None - def __get (self, name, path): - if self.__dict__[name] is None: - self.__dict__[name] = os.path.join(system.get_config_path(), path) + def __get (self, name, path): + if self.__dict__[name] is None: + self.__dict__[name] = os.path.join(system.get_config_path(), path) - return self.__dict__[name] + return self.__dict__[name] - def __is_dir(self, path): - name = "_" + path + "_is_dir" - if self.__dict__[name] is None: - self.__dict__[name] = os.path.isdir(self.__class__.__dict__[path](self)) - return self.__dict__[name] - - def use_path (self): - return self.__get("_use_path", "package.use") + def __is_dir(self, path): + name = "_" + path + "_is_dir" + if self.__dict__[name] is None: + self.__dict__[name] = os.path.isdir(self.__class__.__dict__[path](self)) + return self.__dict__[name] + + def use_path (self): + return self.__get("_use_path", "package.use") - def use_path_is_dir (self): - return self.__is_dir("use_path") + def use_path_is_dir (self): + return self.__is_dir("use_path") - def mask_path (self): - return self.__get("_mask_path", "package.mask") + def mask_path (self): + return self.__get("_mask_path", "package.mask") - def mask_path_is_dir (self): - return self.__is_dir("mask_path") + def mask_path_is_dir (self): + return self.__is_dir("mask_path") - def unmask_path (self): - return self.__get("_unmask_path", "package.unmask") + def unmask_path (self): + return self.__get("_unmask_path", "package.unmask") - def unmask_path_is_dir (self): - return self.__is_dir("unmask_path") + def unmask_path_is_dir (self): + return self.__is_dir("unmask_path") - def testing_path (self): - return self.__get("_testing_path", "package.keywords") + def testing_path (self): + return self.__get("_testing_path", "package.keywords") - def testing_path_is_dir (self): - return self.__is_dir("testing_path") + def testing_path_is_dir (self): + return self.__is_dir("testing_path") CONST = Constants() ### GENERAL PART ### def grep (pkg, path): - """Grep runs "egrep" on a given path and looks for occurences of a given package. - @param pkg: the package - @type pkg: string (cpv) or L{backend.Package}-object - @param path: path to look in - @type path: string - - @returns: occurences of pkg in the format: "file:line-no:complete_line_found" - @rtype: string""" - - if not is_package(pkg): - pkg = system.new_package(pkg) # assume it is a cpv or a gentoolkit.Package - - if os.path.exists(path): - command = "egrep -x -n -r -H '^[<>!=~]{0,2}%s(-[0-9].*)?[[:space:]]?.*$' %s" # %s is replaced in the next line ;) - return Popen((command % (pkg.get_cp(), path)), shell = True, stdout = PIPE).communicate()[0].splitlines() - else: - return [] + """Grep runs "egrep" on a given path and looks for occurences of a given package. + @param pkg: the package + @type pkg: string (cpv) or L{backend.Package}-object + @param path: path to look in + @type path: string + + @returns: occurences of pkg in the format: "file:line-no:complete_line_found" + @rtype: string""" + + if not is_package(pkg): + pkg = system.new_package(pkg) # assume it is a cpv or a gentoolkit.Package + + if os.path.exists(path): + command = "egrep -x -n -r -H '^[<>!=~]{0,2}%s(-[0-9].*)?[[:space:]]?.*$' %s" # %s is replaced in the next line ;) + return Popen((command % (pkg.get_cp(), path)), shell = True, stdout = PIPE).communicate()[0].splitlines() + else: + return [] def get_data(pkg, path): - """This splits up the data of L{grep} and builds tuples in the format (file,line,criterion,list_of_flags). - @param pkg: package to find - @type pkg: string (cpv) or L{backend.Package}-object - @param path: path to look in - @type path: string - - @returns: a list of tuples in the form (file,line,criterion,list_of_flags) - @rtype: (string,string,string,string[])[]""" - - flags = [] - - # do grep - list = grep(pkg, path) - - for i in range(len(list)): - file, line, fl = tuple(list[i].split(":")) # get file, line and flag-list - fl = fl.split() - crit = fl[0] - fl = fl[1:] - # stop after first comment - for j in range(len(fl)): - if fl[j][0] == "#": # comment - stop here - fl = fl[:j] - break - flags.append((file,line,crit,fl)) - - return flags + """This splits up the data of L{grep} and builds tuples in the format (file,line,criterion,list_of_flags). + @param pkg: package to find + @type pkg: string (cpv) or L{backend.Package}-object + @param path: path to look in + @type path: string + + @returns: a list of tuples in the form (file,line,criterion,list_of_flags) + @rtype: (string,string,string,string[])[]""" + + flags = [] + + # do grep + list = grep(pkg, path) + + for i in range(len(list)): + file, line, fl = tuple(list[i].split(":")) # get file, line and flag-list + fl = fl.split() + crit = fl[0] + fl = fl[1:] + # stop after first comment + for j in range(len(fl)): + if fl[j][0] == "#": # comment - stop here + fl = fl[:j] + break + flags.append((file,line,crit,fl)) + + return flags def set_config (cfg): - """This function sets the CONFIG-variable for the whole module. Use this instead of modifying L{CONFIG} directly. - @param cfg: a dictionary with at least all the keys of the CONFIG-var - @type cfg: dict - @raises KeyError: if a keyword is missing in the new cfg""" + """This function sets the CONFIG-variable for the whole module. Use this instead of modifying L{CONFIG} directly. + @param cfg: a dictionary with at least all the keys of the CONFIG-var + @type cfg: dict + @raises KeyError: if a keyword is missing in the new cfg""" - for i in CONFIG.keys(): - if not i in cfg: - raise KeyError, "Missing keyword in config: "+i + for i in CONFIG.keys(): + if not i in cfg: + raise KeyError, "Missing keyword in config: "+i - for i in CONFIG: - CONFIG[i] = cfg[i] + for i in CONFIG: + CONFIG[i] = cfg[i] def generate_path (cpv, exp): - """Generates the correct path out of given wildcards. - These wildcards can be: - - $(cat) : category - - $(cat-1): first part of the category (e.g. "app") - - $(cat-2): second part of the category - - $(pkg) : name of the package - - $(version) : version of the package - - @param cpv: the cpv of the current package - @type cpv: string (cat/pkg-ver) - @param exp: the expression to render the path from - @type exp: string - @returns: rendered path - @rtype string""" - - if exp.find("$(") != -1: - cat, pkg, ver, rev = system.split_cpv(cpv) - if rev != "r0": - ver = "%s-%s" % (ver, rev) - exp = exp.replace("$(cat)",cat).\ - replace("$(pkg)",pkg).\ - replace("$(cat-1)",cat.split("-")[0]).\ - replace("$(cat-2)",cat.split("-")[1]).\ - replace("$(version)",ver) - return exp + """Generates the correct path out of given wildcards. + These wildcards can be: + - $(cat) : category + - $(cat-1): first part of the category (e.g. "app") + - $(cat-2): second part of the category + - $(pkg) : name of the package + - $(version) : version of the package + + @param cpv: the cpv of the current package + @type cpv: string (cat/pkg-ver) + @param exp: the expression to render the path from + @type exp: string + @returns: rendered path + @rtype string""" + + if exp.find("$(") != -1: + cat, pkg, ver, rev = system.split_cpv(cpv) + if rev != "r0": + ver = "%s-%s" % (ver, rev) + exp = exp.replace("$(cat)",cat).\ + replace("$(pkg)",pkg).\ + replace("$(cat-1)",cat.split("-")[0]).\ + replace("$(cat-2)",cat.split("-")[1]).\ + replace("$(version)",ver) + return exp ### USE FLAG PART ### useFlags = {} # useFlags in the file newUseFlags = {} # useFlags as we want them to be: format: cpv -> [(file, line, useflag, (true if removed from list / false if added))] def invert_use_flag (flag): - """Invertes a flag. - - >>> invert_use_flag("foo") - -foo - >>> invert_use_flag("-bar") - bar - - @param flag: the flag - @type flag: string - @returns: inverted flag - @rtype: string - """ - - if flag[0] == "-": - return flag[1:] - else: - return "-"+flag + """Invertes a flag. + + >>> invert_use_flag("foo") + -foo + >>> invert_use_flag("-bar") + bar + + @param flag: the flag + @type flag: string + @returns: inverted flag + @rtype: string + """ + + if flag[0] == "-": + return flag[1:] + else: + return "-"+flag def sort_use_flag_list (flaglist): - """ - Sorts a list of useflags. If a use flag starts with "+" or "-" this one is ignored for the matter of sorting. - This functions sorts the list itself - thus does not create a new one. But for convenience it returns the list too. - - @param flaglist: the list of useflags - @type flaglist: string[] - - @returns: the sorted list (Note: it is the same as the one passed in) - @rtype: string[] - """ - - def flag_key (flag): - if flag.startswith(("+","-")): - return flag[1:] - else: - return flag - - flaglist.sort(key = flag_key) - return flaglist + """ + Sorts a list of useflags. If a use flag starts with "+" or "-" this one is ignored for the matter of sorting. + This functions sorts the list itself - thus does not create a new one. But for convenience it returns the list too. + + @param flaglist: the list of useflags + @type flaglist: string[] + + @returns: the sorted list (Note: it is the same as the one passed in) + @rtype: string[] + """ + + def flag_key (flag): + if flag.startswith(("+","-")): + return flag[1:] + else: + return flag + + flaglist.sort(key = flag_key) + return flaglist def filter_defaults (flaglist): - """ - Removes "+" and "-" from IUSE defaults. + """ + Removes "+" and "-" from IUSE defaults. - @param flaglist: the list of useflags - @type flaglist: string + @param flaglist: the list of useflags + @type flaglist: string - @returns: the "cleaned" list - @rtype: string - """ + @returns: the "cleaned" list + @rtype: string + """ - for flag in flaglist: - if flag.startswith(("+","-")): - yield flag[1:] - else: - yield flag + for flag in flaglist: + if flag.startswith(("+","-")): + yield flag[1:] + else: + yield flag def set_use_flag (pkg, flag): - """Sets the useflag for a given package. - - @param pkg: the package - @type pkg: string (cpv) or L{backend.Package}-object - @param flag: the flag to set - @type flag: string""" - - global useFlags, newUseFlags - - if not is_package(pkg): - pkg = system.new_package(pkg) # assume cpv or gentoolkit.Package - - cpv = pkg.get_cpv() - invFlag = invert_use_flag(flag) - - # if not saved in useFlags, get it by calling get_data() which calls grep() - data = None - if not cpv in useFlags: - data = get_data(pkg, CONST.use_path()) - useFlags[cpv] = data - else: - data = useFlags[cpv] - - if not cpv in newUseFlags: - newUseFlags[cpv] = [] - - debug("data: %s", str(data)) - # add a useflag / delete one - added = False - for file, line, crit, flags in data: - if pkg.matches(crit): - # we have the inverted flag in the uselist/newuselist --> delete it - if invFlag in flags or (file, line, invFlag, False) in newUseFlags[cpv] or (file, line, flag, True) in newUseFlags[cpv]: - if added: del newUseFlags[-1] # we currently added it as an extra option - delete it - added = True - jumpOut = False - for t in ((file, line, invFlag, False),(file, line, flag, True)): - if t in newUseFlags[cpv]: - newUseFlags[cpv].remove(t) - jumpOut = True - # break # don't break as both cases can be valid (see below) - if not jumpOut: - newUseFlags[cpv].append((file, line, invFlag, True)) - - # we removed the inverted from package.use - but it is still enabled somewhere else - # so set it explicitly here - if invFlag in pkg.get_actual_use_flags(): - newUseFlags[cpv].append((file, line, flag, False)) - break - - # we want to duplicate the flag --> ignore - elif flag in flags: - added = True # emulate adding - break - - # add as an extra flag - else: - if not added: newUseFlags[cpv].append((file, line, flag, False)) - added = True - - # create a new line - if not added: - path = CONST.use_path() - if CONST.use_path_is_dir(): - path = os.path.join(CONST.use_path(), generate_path(cpv, CONFIG["usefile"])) - try: - newUseFlags[cpv].remove((path, -1, invFlag, False)) - except ValueError: # not in UseFlags - newUseFlags[cpv].append((path, -1, flag, False)) - - newUseFlags[cpv] = unique_array(newUseFlags[cpv]) - debug("newUseFlags: %s", str(newUseFlags)) + """Sets the useflag for a given package. + + @param pkg: the package + @type pkg: string (cpv) or L{backend.Package}-object + @param flag: the flag to set + @type flag: string""" + + global useFlags, newUseFlags + + if not is_package(pkg): + pkg = system.new_package(pkg) # assume cpv or gentoolkit.Package + + cpv = pkg.get_cpv() + invFlag = invert_use_flag(flag) + + # if not saved in useFlags, get it by calling get_data() which calls grep() + data = None + if not cpv in useFlags: + data = get_data(pkg, CONST.use_path()) + useFlags[cpv] = data + else: + data = useFlags[cpv] + + if not cpv in newUseFlags: + newUseFlags[cpv] = [] + + debug("data: %s", str(data)) + # add a useflag / delete one + added = False + for file, line, crit, flags in data: + if pkg.matches(crit): + # we have the inverted flag in the uselist/newuselist --> delete it + if invFlag in flags or (file, line, invFlag, False) in newUseFlags[cpv] or (file, line, flag, True) in newUseFlags[cpv]: + if added: del newUseFlags[-1] # we currently added it as an extra option - delete it + added = True + jumpOut = False + for t in ((file, line, invFlag, False),(file, line, flag, True)): + if t in newUseFlags[cpv]: + newUseFlags[cpv].remove(t) + jumpOut = True + # break # don't break as both cases can be valid (see below) + if not jumpOut: + newUseFlags[cpv].append((file, line, invFlag, True)) + + # we removed the inverted from package.use - but it is still enabled somewhere else + # so set it explicitly here + if invFlag in pkg.get_actual_use_flags(): + newUseFlags[cpv].append((file, line, flag, False)) + break + + # we want to duplicate the flag --> ignore + elif flag in flags: + added = True # emulate adding + break + + # add as an extra flag + else: + if not added: newUseFlags[cpv].append((file, line, flag, False)) + added = True + + # create a new line + if not added: + path = CONST.use_path() + if CONST.use_path_is_dir(): + path = os.path.join(CONST.use_path(), generate_path(cpv, CONFIG["usefile"])) + try: + newUseFlags[cpv].remove((path, -1, invFlag, False)) + except ValueError: # not in UseFlags + newUseFlags[cpv].append((path, -1, flag, False)) + + newUseFlags[cpv] = unique_array(newUseFlags[cpv]) + debug("newUseFlags: %s", str(newUseFlags)) def remove_new_use_flags (cpv): - """Removes all new use-flags for a specific package. - - @param cpv: the package for which to remove the flags - @type cpv: string (cpv) or L{backend.Package}-object""" - - if is_package(cpv): - cpv = cpv.get_cpv() - - try: - del newUseFlags[cpv] - except KeyError: - pass + """Removes all new use-flags for a specific package. + + @param cpv: the package for which to remove the flags + @type cpv: string (cpv) or L{backend.Package}-object""" + + if is_package(cpv): + cpv = cpv.get_cpv() + + try: + del newUseFlags[cpv] + except KeyError: + pass def get_new_use_flags (cpv): - """Gets all the new use-flags for a specific package. - - @param cpv: the package from which to get the flags - @type cpv: string (cpv) or L{backend.Package}-object - @returns: list of flags - @rtype: string[]""" - - if is_package(cpv): - cpv = cpv.get_cpv() - - list2return = set() - try: - for file, line, flag, remove in newUseFlags[cpv]: - if remove: - list2return.add("~"+invert_use_flag(flag)) - else: - list2return.add(flag) - except KeyError: - pass - - return list(list2return) + """Gets all the new use-flags for a specific package. + + @param cpv: the package from which to get the flags + @type cpv: string (cpv) or L{backend.Package}-object + @returns: list of flags + @rtype: string[]""" + + if is_package(cpv): + cpv = cpv.get_cpv() + + list2return = set() + try: + for file, line, flag, remove in newUseFlags[cpv]: + if remove: + list2return.add("~"+invert_use_flag(flag)) + else: + list2return.add(flag) + except KeyError: + pass + + return list(list2return) def write_use_flags (): - """This writes our changed useflags into the file.""" - global newUseFlags, useFlags - - def insert (flag, list): - """Shortcut for inserting a new flag right after the package-name.""" - list.insert(1,flag) - - def remove (flag, list): - """Removes a flag.""" - try: - list.remove(flag) - except ValueError: # flag is given as flag\n - list.remove(flag+"\n") - list.append("\n") #re-insert the newline - - # no more flags there - comment it out - if len(list) == 1 or list[1][0] in ("#","\n"): - list[0] = "#"+list[0] - insert("#removed by portato#",list) - - file_cache = {} # cache for having to read the file only once: name->[lines] - for cpv in newUseFlags: - flagsToAdd = [] # this is used for collecting the flags to be inserted in a _new_ line - - newUseFlags[cpv].sort(key = lambda x: x[3]) # now the flags are sorted in a manner, that removal comes after appending - - for file, line, flag, delete in newUseFlags[cpv]: - line = int(line) # it is saved as a string so far! - # add new line - if line == -1: - flagsToAdd.append(flag) - # change a line - else: - if not file in file_cache: - # read file - with open(file, "r") as f: - lines = [] - i = 1 - while i < line: # stop at the given line - lines.append(f.readline()) - i += 1 - l = f.readline().split(" ") - - # delete or insert - if delete: - remove(flag,l) - else: - insert(flag,l) - lines.append(" ".join(l)) - - # read the rest - lines.extend(f.readlines()) - - file_cache[file] = lines - - else: # in cache - l = file_cache[file][line-1].split(" ") - if delete: - remove(flag, l) - else: - insert(flag,l) - file_cache[file][line-1] = " ".join(l) - - if flagsToAdd: - # write new lines - msg = "\n#portato update#\n" - if CONFIG["usePerVersion"]: # add on a per-version-base - msg += "=%s %s\n" % (cpv, ' '.join(flagsToAdd)) - else: # add on a per-package-base - list = system.split_cpv(cpv) - msg += "%s/%s %s\n" % (list[0], list[1], ' '.join(flagsToAdd)) - if not file in file_cache: - f = open(file, "a") - f.write(msg) - f.close() - else: - file_cache[file].append(msg) - - # write to disk - for file in file_cache.keys(): - f = open(file, "w") - f.writelines(file_cache[file]) - f.close() - # reset - useFlags = {} - newUseFlags = {} - system.reload_settings() + """This writes our changed useflags into the file.""" + global newUseFlags, useFlags + + def insert (flag, list): + """Shortcut for inserting a new flag right after the package-name.""" + list.insert(1,flag) + + def remove (flag, list): + """Removes a flag.""" + try: + list.remove(flag) + except ValueError: # flag is given as flag\n + list.remove(flag+"\n") + list.append("\n") #re-insert the newline + + # no more flags there - comment it out + if len(list) == 1 or list[1][0] in ("#","\n"): + list[0] = "#"+list[0] + insert("#removed by portato#",list) + + file_cache = {} # cache for having to read the file only once: name->[lines] + for cpv in newUseFlags: + flagsToAdd = [] # this is used for collecting the flags to be inserted in a _new_ line + + newUseFlags[cpv].sort(key = lambda x: x[3]) # now the flags are sorted in a manner, that removal comes after appending + + for file, line, flag, delete in newUseFlags[cpv]: + line = int(line) # it is saved as a string so far! + # add new line + if line == -1: + flagsToAdd.append(flag) + # change a line + else: + if not file in file_cache: + # read file + with open(file, "r") as f: + lines = [] + i = 1 + while i < line: # stop at the given line + lines.append(f.readline()) + i += 1 + l = f.readline().split(" ") + + # delete or insert + if delete: + remove(flag,l) + else: + insert(flag,l) + lines.append(" ".join(l)) + + # read the rest + lines.extend(f.readlines()) + + file_cache[file] = lines + + else: # in cache + l = file_cache[file][line-1].split(" ") + if delete: + remove(flag, l) + else: + insert(flag,l) + file_cache[file][line-1] = " ".join(l) + + if flagsToAdd: + # write new lines + msg = "\n#portato update#\n" + if CONFIG["usePerVersion"]: # add on a per-version-base + msg += "=%s %s\n" % (cpv, ' '.join(flagsToAdd)) + else: # add on a per-package-base + list = system.split_cpv(cpv) + msg += "%s/%s %s\n" % (list[0], list[1], ' '.join(flagsToAdd)) + if not file in file_cache: + f = open(file, "a") + f.write(msg) + f.close() + else: + file_cache[file].append(msg) + + # write to disk + for file in file_cache.keys(): + f = open(file, "w") + f.writelines(file_cache[file]) + f.close() + # reset + useFlags = {} + newUseFlags = {} + system.reload_settings() ### MASKING PART ### new_masked = {} new_unmasked = {} def set_masked (pkg, masked = True): - """Sets the masking status of the package. - - @param pkg: the package from which to get the flags - @type pkg: string (cpv) or L{backend.Package}-object - @param masked: if True: mask it; if False: unmask it - @type masked: boolean""" - - global new_masked, newunmasked - - if not is_package(pkg): - pkg = system.new_package(pkg) - - cpv = pkg.get_cpv() - - if not cpv in new_unmasked: - new_unmasked[cpv] = [] - if not cpv in new_masked: - new_masked[cpv] = [] - - if masked: - link_neq = new_masked - link_eq = new_unmasked - path = CONST.unmask_path() - else: - link_neq = new_unmasked - link_eq = new_masked - path = CONST.mask_path() - - copy = link_eq[cpv][:] - for file, line in copy: - if line == "-1": - link_eq[cpv].remove((file, line)) - - copy = link_neq[cpv][:] - for file, line in copy: - if line != "-1": - link_neq[cpv].remove((file, line)) - - if masked == pkg.is_masked(): - return - - data = get_data(pkg, path) - debug("data: %s", str(data)) - done = False - for file, line, crit, flags in data: - if pkg.matches(crit): - link_eq[cpv].append((file, line)) - done = True - - if done: return - - if masked: - is_dir = CONST.mask_path_is_dir() - path = CONST.mask_path() - else: - is_dir = CONST.unmask_path_is_dir() - path = CONST.unmask_path() - - if is_dir: - file = os.path.join(path, generate_path(cpv, CONFIG["maskfile"])) - else: - file = path - - link_neq[cpv].append((file, "-1")) - link_neq[cpv] = unique_array(link_neq[cpv]) - debug("new_(un)masked: %s",str(link_neq)) + """Sets the masking status of the package. + + @param pkg: the package from which to get the flags + @type pkg: string (cpv) or L{backend.Package}-object + @param masked: if True: mask it; if False: unmask it + @type masked: boolean""" + + global new_masked, newunmasked + + if not is_package(pkg): + pkg = system.new_package(pkg) + + cpv = pkg.get_cpv() + + if not cpv in new_unmasked: + new_unmasked[cpv] = [] + if not cpv in new_masked: + new_masked[cpv] = [] + + if masked: + link_neq = new_masked + link_eq = new_unmasked + path = CONST.unmask_path() + else: + link_neq = new_unmasked + link_eq = new_masked + path = CONST.mask_path() + + copy = link_eq[cpv][:] + for file, line in copy: + if line == "-1": + link_eq[cpv].remove((file, line)) + + copy = link_neq[cpv][:] + for file, line in copy: + if line != "-1": + link_neq[cpv].remove((file, line)) + + if masked == pkg.is_masked(): + return + + data = get_data(pkg, path) + debug("data: %s", str(data)) + done = False + for file, line, crit, flags in data: + if pkg.matches(crit): + link_eq[cpv].append((file, line)) + done = True + + if done: return + + if masked: + is_dir = CONST.mask_path_is_dir() + path = CONST.mask_path() + else: + is_dir = CONST.unmask_path_is_dir() + path = CONST.unmask_path() + + if is_dir: + file = os.path.join(path, generate_path(cpv, CONFIG["maskfile"])) + else: + file = path + + link_neq[cpv].append((file, "-1")) + link_neq[cpv] = unique_array(link_neq[cpv]) + debug("new_(un)masked: %s",str(link_neq)) def remove_new_masked (cpv): - if is_package(cpv): - cpv = cpv.get_cpv() - - try: - del new_masked[cpv] - except KeyError: - pass - - try: - del new_unmasked[cpv] - except KeyError: - pass + if is_package(cpv): + cpv = cpv.get_cpv() + + try: + del new_masked[cpv] + except KeyError: + pass + + try: + del new_unmasked[cpv] + except KeyError: + pass def new_masking_status (cpv): - if is_package(cpv): - cpv = cpv.get_cpv() - - def get(list): - ret = None - if cpv in list and list[cpv] != []: - for file, line in list[cpv]: - _ret = (int(line) == -1) - if ret is not None and _ret != ret: - error(_("Conflicting values for masking status: %s"), list) - else: - ret = _ret - return ret - - masked = get(new_masked) - if masked is None: - masked = get(new_unmasked) - if masked is not None: - masked = not masked # revert for new_unmasked - - if masked is not None: - if masked: return "masked" - else: return "unmasked" - else: - return None + if is_package(cpv): + cpv = cpv.get_cpv() + + def get(list): + ret = None + if cpv in list and list[cpv] != []: + for file, line in list[cpv]: + _ret = (int(line) == -1) + if ret is not None and _ret != ret: + error(_("Conflicting values for masking status: %s"), list) + else: + ret = _ret + return ret + + masked = get(new_masked) + if masked is None: + masked = get(new_unmasked) + if masked is not None: + masked = not masked # revert for new_unmasked + + if masked is not None: + if masked: return "masked" + else: return "unmasked" + else: + return None def is_locally_masked (pkg, changes = True): - if not is_package(pkg): - pkg = system.new_package(pkg) # assume it is a cpv or a gentoolkit.Package + if not is_package(pkg): + pkg = system.new_package(pkg) # assume it is a cpv or a gentoolkit.Package - if changes: - if new_masking_status(pkg) == "masked": # we masked it ourselves, but did not save it yet - # but sometimes, new_masking_status() returns "masked" if a package's unmask is removed - # then it is masked by the system but not locally (except rarely exotic cases) - if pkg.get_cpv() in new_unmasked: - if new_unmasked[pkg.get_cpv()]: return False # assume that there only exists one entry for this package - # else new_masking_status should have printed an error - return True + if changes: + if new_masking_status(pkg) == "masked": # we masked it ourselves, but did not save it yet + # but sometimes, new_masking_status() returns "masked" if a package's unmask is removed + # then it is masked by the system but not locally (except rarely exotic cases) + if pkg.get_cpv() in new_unmasked: + if new_unmasked[pkg.get_cpv()]: return False # assume that there only exists one entry for this package + # else new_masking_status should have printed an error + return True - if new_masking_status(pkg) == "unmasked": # we unmasked it - return False - - list = get_data(pkg, CONST.mask_path()) + if new_masking_status(pkg) == "unmasked": # we unmasked it + return False + + list = get_data(pkg, CONST.mask_path()) - if not list: return False + if not list: return False - for file, line, crit, fl in list: - if pkg.matches(crit): - return True + for file, line, crit, fl in list: + if pkg.matches(crit): + return True - return False - + return False + def write_masked (): - global new_unmasked, new_masked - file_cache = {} - - def write(cpv, file, line): - line = int(line) - # add new line - if line == -1: - msg = "\n#portato update#\n" - if CONFIG["maskPerVersion"]: - msg += "=%s\n" % cpv - else: - list = system.split_cpv(cpv) - msg += "%s/%s\n" % (list[0],list[1]) - if not file in file_cache: - f = open(file, "a") - f.write(msg) - f.close() - else: - file_cache[file].append(msg) - # change a line - else: - if not file in file_cache: - # read file - f = open(file, "r") - lines = [] - i = 1 - while i < line: # stop at the given line - lines.append(f.readline()) - i = i+1 - # delete - l = f.readline() - l = "#"+l[:-1]+" # removed by portato\n" - lines.append(l) - - # read the rest - lines.extend(f.readlines()) - - file_cache[file] = lines - f.close() - else: # in cache - l = file_cache[file][line-1] - # delete: - l = "#"+l[:-1]+" # removed by portato\n" - file_cache[file][line-1] = l - - - for cpv in new_masked: - for file, line in new_masked[cpv]: - write(cpv, file, line) - - for cpv in new_unmasked: - for file, line in new_unmasked[cpv]: - write(cpv, file, line) - - # write to disk - for file in file_cache.keys(): - f = open(file, "w") - f.writelines(file_cache[file]) - f.close() - # reset - new_masked = {} - new_unmasked = {} - system.reload_settings() + global new_unmasked, new_masked + file_cache = {} + + def write(cpv, file, line): + line = int(line) + # add new line + if line == -1: + msg = "\n#portato update#\n" + if CONFIG["maskPerVersion"]: + msg += "=%s\n" % cpv + else: + list = system.split_cpv(cpv) + msg += "%s/%s\n" % (list[0],list[1]) + if not file in file_cache: + f = open(file, "a") + f.write(msg) + f.close() + else: + file_cache[file].append(msg) + # change a line + else: + if not file in file_cache: + # read file + f = open(file, "r") + lines = [] + i = 1 + while i < line: # stop at the given line + lines.append(f.readline()) + i = i+1 + # delete + l = f.readline() + l = "#"+l[:-1]+" # removed by portato\n" + lines.append(l) + + # read the rest + lines.extend(f.readlines()) + + file_cache[file] = lines + f.close() + else: # in cache + l = file_cache[file][line-1] + # delete: + l = "#"+l[:-1]+" # removed by portato\n" + file_cache[file][line-1] = l + + + for cpv in new_masked: + for file, line in new_masked[cpv]: + write(cpv, file, line) + + for cpv in new_unmasked: + for file, line in new_unmasked[cpv]: + write(cpv, file, line) + + # write to disk + for file in file_cache.keys(): + f = open(file, "w") + f.writelines(file_cache[file]) + f.close() + # reset + new_masked = {} + new_unmasked = {} + system.reload_settings() ### TESTING PART ### newTesting = {} arch = "" def remove_new_testing (cpv): - if is_package(cpv): - cpv = cpv.get_cpv() - - try: - del newTesting[cpv] - except KeyError: - pass + if is_package(cpv): + cpv = cpv.get_cpv() + + try: + del newTesting[cpv] + except KeyError: + pass def new_testing_status (cpv): - if is_package(cpv): - cpv = cpv.get_cpv() + if is_package(cpv): + cpv = cpv.get_cpv() - if cpv in newTesting: - for file, line in newTesting[cpv]: - if line == "-1": return False - else: return True + if cpv in newTesting: + for file, line in newTesting[cpv]: + if line == "-1": return False + else: return True - return None + return None def set_testing (pkg, enable): - """Enables the package for installing when it is marked as testing (~ARCH). - @param pkg: the package - @type pkg: string (cpv) or L{backend.Package}-object - @param enable: controls whether to enable (True) or disable (False) for test-installing - @type enable: boolean""" - - global arch, newTesting - if not is_package(pkg): - pkg = system.new_package(pkg) - - arch = pkg.get_global_settings("ARCH") - cpv = pkg.get_cpv() - if not cpv in newTesting: - newTesting[cpv] = [] - - for file, line in newTesting[cpv]: - if (enable and line != "-1") or (not enable and line == "-1"): - newTesting[cpv].remove((file, line)) - - if (enable and not pkg.is_testing()) or (not enable and pkg.is_testing()): - return - - if not enable: - test = get_data(pkg, CONST.testing_path()) - debug("data (test): %s", str(test)) - for file, line, crit, flags in test: - if pkg.matches(crit) and flags[0] == "~"+arch: - newTesting[cpv].append((file, line)) - else: - if CONST.testing_path_is_dir(): - file = os.path.join(CONST.testing_path(), generate_path(cpv, CONFIG["testingfile"])) - else: - file = CONST.testing_path() - newTesting[cpv].append((file, "-1")) - - newTesting[cpv] = unique_array(newTesting[cpv]) - debug("newTesting: %s",str(newTesting)) + """Enables the package for installing when it is marked as testing (~ARCH). + @param pkg: the package + @type pkg: string (cpv) or L{backend.Package}-object + @param enable: controls whether to enable (True) or disable (False) for test-installing + @type enable: boolean""" + + global arch, newTesting + if not is_package(pkg): + pkg = system.new_package(pkg) + + arch = pkg.get_global_settings("ARCH") + cpv = pkg.get_cpv() + if not cpv in newTesting: + newTesting[cpv] = [] + + for file, line in newTesting[cpv]: + if (enable and line != "-1") or (not enable and line == "-1"): + newTesting[cpv].remove((file, line)) + + if (enable and not pkg.is_testing()) or (not enable and pkg.is_testing()): + return + + if not enable: + test = get_data(pkg, CONST.testing_path()) + debug("data (test): %s", str(test)) + for file, line, crit, flags in test: + if pkg.matches(crit) and flags[0] == "~"+arch: + newTesting[cpv].append((file, line)) + else: + if CONST.testing_path_is_dir(): + file = os.path.join(CONST.testing_path(), generate_path(cpv, CONFIG["testingfile"])) + else: + file = CONST.testing_path() + newTesting[cpv].append((file, "-1")) + + newTesting[cpv] = unique_array(newTesting[cpv]) + debug("newTesting: %s",str(newTesting)) def write_testing (): - global arch, newTesting - file_cache = {} - - for cpv in newTesting: - for file, line in newTesting[cpv]: - line = int(line) - # add new line - if line == -1: - msg = "\n#portato update#\n" - if CONFIG["testingPerVersion"]: - msg += "=%s ~%s\n" % (cpv, arch) - else: - list = system.split_cpv(cpv) - msg += "%s/%s ~%s\n" % (list[0],list[1],arch) - if not file in file_cache: - f = open(file, "a") - f.write(msg) - f.close() - else: - file_cache[file].append(msg) - # change a line - else: - if not file in file_cache: - # read file - f = open(file, "r") - lines = [] - i = 1 - while i < line: # stop at the given line - lines.append(f.readline()) - i = i+1 - # delete - l = f.readline() - l = "#"+l[:-1]+" # removed by portato\n" - lines.append(l) - - # read the rest - lines.extend(f.readlines()) - - file_cache[file] = lines - f.close() - else: # in cache - l = file_cache[file][line-1] - # delete: - l = "#"+l[:-1]+" # removed by portato\n" - file_cache[file][line-1] = l - - # write to disk - for file in file_cache.keys(): - f = open(file, "w") - f.writelines(file_cache[file]) - f.close() - # reset - newTesting = {} - system.reload_settings() + global arch, newTesting + file_cache = {} + + for cpv in newTesting: + for file, line in newTesting[cpv]: + line = int(line) + # add new line + if line == -1: + msg = "\n#portato update#\n" + if CONFIG["testingPerVersion"]: + msg += "=%s ~%s\n" % (cpv, arch) + else: + list = system.split_cpv(cpv) + msg += "%s/%s ~%s\n" % (list[0],list[1],arch) + if not file in file_cache: + f = open(file, "a") + f.write(msg) + f.close() + else: + file_cache[file].append(msg) + # change a line + else: + if not file in file_cache: + # read file + f = open(file, "r") + lines = [] + i = 1 + while i < line: # stop at the given line + lines.append(f.readline()) + i = i+1 + # delete + l = f.readline() + l = "#"+l[:-1]+" # removed by portato\n" + lines.append(l) + + # read the rest + lines.extend(f.readlines()) + + file_cache[file] = lines + f.close() + else: # in cache + l = file_cache[file][line-1] + # delete: + l = "#"+l[:-1]+" # removed by portato\n" + file_cache[file][line-1] = l + + # write to disk + for file in file_cache.keys(): + f = open(file, "w") + f.writelines(file_cache[file]) + f.close() + # reset + newTesting = {} + system.reload_settings() diff --git a/portato/backend/package.py b/portato/backend/package.py index 6d73a42..e4b2082 100644 --- a/portato/backend/package.py +++ b/portato/backend/package.py @@ -18,396 +18,396 @@ from ..dependency import DependencyTree from . import _Package, system, flags class Package (_Package): - """This is a class abstracting a normal package which can be installed.""" - - def __init__ (self, cpv): - """Constructor. - - @param cpv: The cpv which describes the package to create. - @type cpv: string (cat/pkg-ver)""" - - self._cpv = cpv - - # - # implemented - # - - def set_testing(self, enable = True): - """Sets the actual testing status of the package. - - @param enable: if True it is masked as stable; if False it is marked as testing - @type enable: boolean""" - - flags.set_testing(self, enable) - - def remove_new_testing(self): - """Removes possible changed testing status.""" - - flags.remove_new_testing(self.get_cpv()) - - def set_masked (self, masking = False): - """Sets the masking status of the package. - - @param masking: if True: mask it; if False: unmask it - @type masking: boolean""" - - flags.set_masked(self, masked = masking) - - def remove_new_masked (self): - """Removes possible changed masking status.""" - - flags.remove_new_masked(self.get_cpv()) - - def is_locally_masked (self): - """Checks whether the package is masked by the user. - - @returns: True if masked by the user; False if not - @rtype: bool""" - - return flags.is_locally_masked(self) - - def get_new_use_flags (self): - """Returns a list of the new useflags, i.e. these flags which are not written to the portage-system yet. - - @returns: list of flags or [] - @rtype: string[]""" - - return flags.get_new_use_flags(self) - - def get_actual_use_flags (self): - """This returns all currently set use-flags including the new ones. - - @return: list of flags - @rtype: string[]""" - - i_flags = self.get_global_settings("USE", installed = False).split() - m_flags = system.get_global_settings("USE").split() - for f in self.get_new_use_flags(): - removed = False - - if f[0] == "~": - f = f[1:] - removed = True - - invf = flags.invert_use_flag(f) - - if f[0] == '-': - if invf in i_flags and not (removed and invf in m_flags): - i_flags.remove(invf) - - elif f not in i_flags: - if not (removed and invf in m_flags): - i_flags.append(f) - - return i_flags - - def set_use_flag (self, flag): - """Set a use-flag. - - @param flag: the flag to set - @type flag: string""" - - flags.set_use_flag(self, flag) - - def remove_new_use_flags (self): - """Remove all the new use-flags.""" - - flags.remove_new_use_flags(self) - - def use_expanded (self, flag, suggest = None): - """Tests whether a useflag is an expanded one. If it is, this method returns the USE_EXPAND-value. - - @param flag: the flag to check - @type flag: string - @param suggest: try this suggestion first - @type suggest: string - @returns: USE_EXPAND-value on success - @rtype: string or None""" - - if suggest is not None: - if flag.startswith(suggest.lower()): - return suggest - - for exp in self.get_global_settings("USE_EXPAND").split(): - lexp = exp.lower() - if flag.startswith(lexp): - return exp - - return None - - def get_cpv(self): - """Returns full Category/Package-Version string. - - @returns: the cpv - @rtype: string""" - - return self._cpv - - def get_cp (self): - """Returns the cp-string. - - @returns: category/package. - @rtype: string""" - - return self.get_category()+"/"+self.get_name() - - def get_slot_cp (self): - """Returns the current cp followed by a colon and the slot-number. - - @returns: cp:slot - @rtype: string""" - - return ("%s:%s" % (self.get_cp(), self.get_package_settings("SLOT"))) - - def get_package_path(self): - """Returns the path to where the ChangeLog, Manifest, .ebuild files reside. - - @returns: path to the package files - @rtype: string""" - - p = self.get_ebuild_path() - sp = p.split("/") - if sp: - return "/".join(sp[:-1]) - - def get_dependencies (self): - """ - Returns the tree of dependencies that this package needs. - - @rtype: L{DependencyTree} - """ - deps = " ".join(map(self.get_package_settings, ("RDEPEND", "PDEPEND", "DEPEND"))) - deps = paren_reduce(deps) - - tree = DependencyTree() - - def add (tree, deps): - it = iter(deps) - for dep in it: - if hasattr(dep, "__iter__"): - debug("Following dep is an unsupposed list: %s", dep) - assert(len(dep) == 1) - dep = dep[0] - if dep.endswith("?"): - ntree = tree.add_flag(dep[:-1]) - n = it.next() - if not hasattr(n, "__iter__"): - n = (n,) - add(ntree, n) - - elif dep == "||": - n = it.next() # skip - if not hasattr(n, "__iter__"): - n = [n] - else: - n = list(n) - - tree.add_or(n) - - else: - tree.add(dep) - - add(tree, deps) - return tree - - # - # Not implemented - # - - def get_name(self): - """Returns base name of package, no category nor version. - - @returns: base-name - @rtype: string""" - - raise NotImplementedError - - def get_version(self): - """Returns version of package, with (optional) revision number. - - @returns: version-rev - @rtype: string""" - - raise NotImplementedError - - def get_category(self): - """Returns category of package. - - @returns: category - @rtype: string""" - - raise NotImplementedError - - def is_installed(self): - """Returns true if this package is installed (merged). - @rtype: boolean""" - - raise NotImplementedError - - def is_in_overlay(self): - """Returns true if the package is in an overlay. - @rtype: boolean""" - - raise NotImplementedError - - def get_overlay_path(self): - """Returns the path to the current overlay. - @rtype: string""" - - raise NotImplementedError - - def is_in_system (self): - """Returns False if the package could not be found in the portage system. - - @return: True if in portage system; else False - @rtype: boolean""" - - raise NotImplementedError - - def is_missing_keyword(self): - """Returns True if the package is missing the needed keyword. - - @return: True if keyword is missing; else False - @rtype: boolean""" - - raise NotImplementedError - - def is_testing(self, use_keywords = True): - """Checks whether a package is marked as testing. - - @param use_keywords: Controls whether possible keywords are taken into account or not. - @type use_keywords: boolean - @returns: True if the package is marked as testing; else False. - @rtype: boolean""" - - raise NotImplementedError - - def is_masked (self, use_changed = True): - """Returns True if either masked by package.mask or by profile. - - @param use_changed: Controls, whether changes applied to masking keywords are taken into account. - @type use_changed: boolean - @returns: True if masked / False otherwise - @rtype: boolean""" - - raise NotImplementedError - - def get_masking_reason (self): - """Returns the reason for masking the package. If this is not possible for the system, return None. - - @returns: the reason for masking the package - @rtype: string""" - - def get_iuse_flags (self, installed = False, removeForced = True): - """Returns a list of _all_ useflags for this package, i.e. all useflags you can set for this package. - - @param installed: do not take the ones stated in the ebuild, but the ones it has been installed with - @type installed: boolean - @param removeForced: remove forced flags (i.e. usemask / useforce) from the iuse flags as they cannot be set from the user - @type removeForced: boolean - - @returns: list of use-flags - @rtype: string[]""" - - raise NotImplementedError - - def get_matched_dep_packages (self, depvar): - """This function looks for all dependencies which are resolved. In normal case it makes only sense for installed packages, but should work for uninstalled ones too. - - @param depvar: the dependency variables (RDEPEND, PDEPEND, DEPEND) to use - @type depvar: string[] - - @returns: unique list of dependencies resolved (with elements like "<=net-im/foobar-1.2.3") - @rtype: string[] - - @raises portato.DependencyCalcError: when an error occured during executing portage.dep_check()""" - - raise NotImplementedError - - def get_dep_packages (self, depvar = ["RDEPEND", "PDEPEND", "DEPEND"], with_criterions = False): - """Returns a cpv-list of packages on which this package depends and which have not been installed yet. This does not check the dependencies in a recursive manner. - - @param depvar: the dependency variables (RDEPEND, PDEPEND, DEPEND) to use - @type depvar: string[] - @param with_criterions: return also the criterions - @type with_criterions: boolean - - @returns: list of cpvs on which the package depend (and if wanted also the criterions) - @rtype: string[] or (string, string)[] - - @raises portato.BlockedException: when a package in the dependency-list is blocked by an installed one - @raises portato.PackageNotFoundException: when a package in the dependency list could not be found in the system - @raises portato.DependencyCalcError: when an error occured during executing portage.dep_check()""" - - raise NotImplementedError - - def get_global_settings(self, key, installed = True): - """Returns the value of a global setting, i.e. ARCH, USE, ROOT, DISTDIR etc. - - @param key: the setting to return - @type key: string - @param installed: get the installed settings or the ebuild settings - @type installed: boolean - @returns: the value of this setting - @rtype: string""" - - raise NotImplementedError - - def get_ebuild_path(self): - """Returns the complete path to the .ebuild file. - - @rtype: string""" - - raise NotImplementedError - - def get_files (self): - """ - Returns an iterator over the installed files of a package. - If the package is not installed, the iterator should be "empty". - - @returns: the installed files - @rtype: string - """ - - raise NotImplementedError - - def get_package_settings(self, var, installed = True): - """Returns a package specific setting, such as DESCRIPTION, SRC_URI, IUSE ... - - @param var: the setting to get - @type var: string - @param installed: take the vartree or the porttree - @type installed: boolean - - @returns: the value of the setting - @rtype: string""" - - raise NotImplementedError - - def get_installed_use_flags(self): - """Returns _all_ (not only the package-specific) useflags which were set at the installation time of the package. - - @returns: list of use flags - @rtype: string[]""" - - raise NotImplementedError - - def compare_version(self, other): - """Compares this package's version to another's CPV; returns -1, 0, 1. - - @param other: the other package - @type other: Package - @returns: -1, 0 or 1 - @rtype: int""" - - raise NotImplementedError - - def matches (self, criterion): - """This checks, whether this package matches a specific versioning criterion - e.g.: "<=net-im/foobar-1.2". - - @param criterion: the criterion to match against - @type criterion: string - @returns: True if matches; False if not - @rtype: boolean""" - - raise NotImplementedError + """This is a class abstracting a normal package which can be installed.""" + + def __init__ (self, cpv): + """Constructor. + + @param cpv: The cpv which describes the package to create. + @type cpv: string (cat/pkg-ver)""" + + self._cpv = cpv + + # + # implemented + # + + def set_testing(self, enable = True): + """Sets the actual testing status of the package. + + @param enable: if True it is masked as stable; if False it is marked as testing + @type enable: boolean""" + + flags.set_testing(self, enable) + + def remove_new_testing(self): + """Removes possible changed testing status.""" + + flags.remove_new_testing(self.get_cpv()) + + def set_masked (self, masking = False): + """Sets the masking status of the package. + + @param masking: if True: mask it; if False: unmask it + @type masking: boolean""" + + flags.set_masked(self, masked = masking) + + def remove_new_masked (self): + """Removes possible changed masking status.""" + + flags.remove_new_masked(self.get_cpv()) + + def is_locally_masked (self): + """Checks whether the package is masked by the user. + + @returns: True if masked by the user; False if not + @rtype: bool""" + + return flags.is_locally_masked(self) + + def get_new_use_flags (self): + """Returns a list of the new useflags, i.e. these flags which are not written to the portage-system yet. + + @returns: list of flags or [] + @rtype: string[]""" + + return flags.get_new_use_flags(self) + + def get_actual_use_flags (self): + """This returns all currently set use-flags including the new ones. + + @return: list of flags + @rtype: string[]""" + + i_flags = self.get_global_settings("USE", installed = False).split() + m_flags = system.get_global_settings("USE").split() + for f in self.get_new_use_flags(): + removed = False + + if f[0] == "~": + f = f[1:] + removed = True + + invf = flags.invert_use_flag(f) + + if f[0] == '-': + if invf in i_flags and not (removed and invf in m_flags): + i_flags.remove(invf) + + elif f not in i_flags: + if not (removed and invf in m_flags): + i_flags.append(f) + + return i_flags + + def set_use_flag (self, flag): + """Set a use-flag. + + @param flag: the flag to set + @type flag: string""" + + flags.set_use_flag(self, flag) + + def remove_new_use_flags (self): + """Remove all the new use-flags.""" + + flags.remove_new_use_flags(self) + + def use_expanded (self, flag, suggest = None): + """Tests whether a useflag is an expanded one. If it is, this method returns the USE_EXPAND-value. + + @param flag: the flag to check + @type flag: string + @param suggest: try this suggestion first + @type suggest: string + @returns: USE_EXPAND-value on success + @rtype: string or None""" + + if suggest is not None: + if flag.startswith(suggest.lower()): + return suggest + + for exp in self.get_global_settings("USE_EXPAND").split(): + lexp = exp.lower() + if flag.startswith(lexp): + return exp + + return None + + def get_cpv(self): + """Returns full Category/Package-Version string. + + @returns: the cpv + @rtype: string""" + + return self._cpv + + def get_cp (self): + """Returns the cp-string. + + @returns: category/package. + @rtype: string""" + + return self.get_category()+"/"+self.get_name() + + def get_slot_cp (self): + """Returns the current cp followed by a colon and the slot-number. + + @returns: cp:slot + @rtype: string""" + + return ("%s:%s" % (self.get_cp(), self.get_package_settings("SLOT"))) + + def get_package_path(self): + """Returns the path to where the ChangeLog, Manifest, .ebuild files reside. + + @returns: path to the package files + @rtype: string""" + + p = self.get_ebuild_path() + sp = p.split("/") + if sp: + return "/".join(sp[:-1]) + + def get_dependencies (self): + """ + Returns the tree of dependencies that this package needs. + + @rtype: L{DependencyTree} + """ + deps = " ".join(map(self.get_package_settings, ("RDEPEND", "PDEPEND", "DEPEND"))) + deps = paren_reduce(deps) + + tree = DependencyTree() + + def add (tree, deps): + it = iter(deps) + for dep in it: + if hasattr(dep, "__iter__"): + debug("Following dep is an unsupposed list: %s", dep) + assert(len(dep) == 1) + dep = dep[0] + if dep.endswith("?"): + ntree = tree.add_flag(dep[:-1]) + n = it.next() + if not hasattr(n, "__iter__"): + n = (n,) + add(ntree, n) + + elif dep == "||": + n = it.next() # skip + if not hasattr(n, "__iter__"): + n = [n] + else: + n = list(n) + + tree.add_or(n) + + else: + tree.add(dep) + + add(tree, deps) + return tree + + # + # Not implemented + # + + def get_name(self): + """Returns base name of package, no category nor version. + + @returns: base-name + @rtype: string""" + + raise NotImplementedError + + def get_version(self): + """Returns version of package, with (optional) revision number. + + @returns: version-rev + @rtype: string""" + + raise NotImplementedError + + def get_category(self): + """Returns category of package. + + @returns: category + @rtype: string""" + + raise NotImplementedError + + def is_installed(self): + """Returns true if this package is installed (merged). + @rtype: boolean""" + + raise NotImplementedError + + def is_in_overlay(self): + """Returns true if the package is in an overlay. + @rtype: boolean""" + + raise NotImplementedError + + def get_overlay_path(self): + """Returns the path to the current overlay. + @rtype: string""" + + raise NotImplementedError + + def is_in_system (self): + """Returns False if the package could not be found in the portage system. + + @return: True if in portage system; else False + @rtype: boolean""" + + raise NotImplementedError + + def is_missing_keyword(self): + """Returns True if the package is missing the needed keyword. + + @return: True if keyword is missing; else False + @rtype: boolean""" + + raise NotImplementedError + + def is_testing(self, use_keywords = True): + """Checks whether a package is marked as testing. + + @param use_keywords: Controls whether possible keywords are taken into account or not. + @type use_keywords: boolean + @returns: True if the package is marked as testing; else False. + @rtype: boolean""" + + raise NotImplementedError + + def is_masked (self, use_changed = True): + """Returns True if either masked by package.mask or by profile. + + @param use_changed: Controls, whether changes applied to masking keywords are taken into account. + @type use_changed: boolean + @returns: True if masked / False otherwise + @rtype: boolean""" + + raise NotImplementedError + + def get_masking_reason (self): + """Returns the reason for masking the package. If this is not possible for the system, return None. + + @returns: the reason for masking the package + @rtype: string""" + + def get_iuse_flags (self, installed = False, removeForced = True): + """Returns a list of _all_ useflags for this package, i.e. all useflags you can set for this package. + + @param installed: do not take the ones stated in the ebuild, but the ones it has been installed with + @type installed: boolean + @param removeForced: remove forced flags (i.e. usemask / useforce) from the iuse flags as they cannot be set from the user + @type removeForced: boolean + + @returns: list of use-flags + @rtype: string[]""" + + raise NotImplementedError + + def get_matched_dep_packages (self, depvar): + """This function looks for all dependencies which are resolved. In normal case it makes only sense for installed packages, but should work for uninstalled ones too. + + @param depvar: the dependency variables (RDEPEND, PDEPEND, DEPEND) to use + @type depvar: string[] + + @returns: unique list of dependencies resolved (with elements like "<=net-im/foobar-1.2.3") + @rtype: string[] + + @raises portato.DependencyCalcError: when an error occured during executing portage.dep_check()""" + + raise NotImplementedError + + def get_dep_packages (self, depvar = ["RDEPEND", "PDEPEND", "DEPEND"], with_criterions = False): + """Returns a cpv-list of packages on which this package depends and which have not been installed yet. This does not check the dependencies in a recursive manner. + + @param depvar: the dependency variables (RDEPEND, PDEPEND, DEPEND) to use + @type depvar: string[] + @param with_criterions: return also the criterions + @type with_criterions: boolean + + @returns: list of cpvs on which the package depend (and if wanted also the criterions) + @rtype: string[] or (string, string)[] + + @raises portato.BlockedException: when a package in the dependency-list is blocked by an installed one + @raises portato.PackageNotFoundException: when a package in the dependency list could not be found in the system + @raises portato.DependencyCalcError: when an error occured during executing portage.dep_check()""" + + raise NotImplementedError + + def get_global_settings(self, key, installed = True): + """Returns the value of a global setting, i.e. ARCH, USE, ROOT, DISTDIR etc. + + @param key: the setting to return + @type key: string + @param installed: get the installed settings or the ebuild settings + @type installed: boolean + @returns: the value of this setting + @rtype: string""" + + raise NotImplementedError + + def get_ebuild_path(self): + """Returns the complete path to the .ebuild file. + + @rtype: string""" + + raise NotImplementedError + + def get_files (self): + """ + Returns an iterator over the installed files of a package. + If the package is not installed, the iterator should be "empty". + + @returns: the installed files + @rtype: string + """ + + raise NotImplementedError + + def get_package_settings(self, var, installed = True): + """Returns a package specific setting, such as DESCRIPTION, SRC_URI, IUSE ... + + @param var: the setting to get + @type var: string + @param installed: take the vartree or the porttree + @type installed: boolean + + @returns: the value of the setting + @rtype: string""" + + raise NotImplementedError + + def get_installed_use_flags(self): + """Returns _all_ (not only the package-specific) useflags which were set at the installation time of the package. + + @returns: list of use flags + @rtype: string[]""" + + raise NotImplementedError + + def compare_version(self, other): + """Compares this package's version to another's CPV; returns -1, 0, 1. + + @param other: the other package + @type other: Package + @returns: -1, 0 or 1 + @rtype: int""" + + raise NotImplementedError + + def matches (self, criterion): + """This checks, whether this package matches a specific versioning criterion - e.g.: "<=net-im/foobar-1.2". + + @param criterion: the criterion to match against + @type criterion: string + @returns: True if matches; False if not + @rtype: boolean""" + + raise NotImplementedError diff --git a/portato/backend/portage/__init__.py b/portato/backend/portage/__init__.py index be6cce6..02a4a82 100644 --- a/portato/backend/portage/__init__.py +++ b/portato/backend/portage/__init__.py @@ -17,8 +17,8 @@ from portage import VERSION as PV VERSION = tuple(map(int, (x.split("_")[0] for x in PV.split(".")))) if VERSION >= (2, 2): - from .system_22 import PortageSystem_22 as PortageSystem - from .package_22 import PortagePackage_22 as PortagePackage + from .system_22 import PortageSystem_22 as PortageSystem + from .package_22 import PortagePackage_22 as PortagePackage else: - from .system import PortageSystem - from .package import PortagePackage + from .system import PortageSystem + from .package import PortagePackage diff --git a/portato/backend/portage/package.py b/portato/backend/portage/package.py index 351b7e0..0270029 100644 --- a/portato/backend/portage/package.py +++ b/portato/backend/portage/package.py @@ -21,294 +21,294 @@ from ...helper import debug, error, unique_array import portage try: - import portage.dep as portage_dep + import portage.dep as portage_dep except ImportError: - import portage_dep + import portage_dep import os.path class PortagePackage (Package): - """This is a class abstracting a normal package which can be installed for the portage-system.""" - - def __init__ (self, cpv): - """Constructor. - - @param cpv: The cpv which describes the package to create. - @type cpv: string (cat/pkg-ver)""" - - Package.__init__(self, cpv) - self._scpv = system.split_cpv(self._cpv) - - if not self._scpv: - raise ValueError("invalid cpv: %s" % cpv) - - self._settings = system.settings - self._settingslock = system.settings.settingslock - self._settings_installed = None - - self._trees = system.settings.trees - - self.forced_flags = set() - - with self._settingslock: - self._init_settings(True) - self.forced_flags.update(self._settings.settings.usemask) - self.forced_flags.update(self._settings.settings.useforce) - - try: - self._status = portage.getmaskingstatus(self.get_cpv(), settings = self._settings.settings) - except KeyError: # package is not located in the system - self._status = None - - if self._status and len(self._status) == 1 and self._status[0] == "corrupted": - self._status = None - - def _init_settings (self, installed): - inst = (installed and self.is_installed()) or (self.is_installed() and not self.is_in_system()) - - if self._settings_installed is not None and self._settings_installed != inst: - self._settings.settings.reset() - - self._settings_installed = inst - - if inst: - dbapi = self._settings.vartree.dbapi - else: - dbapi = self._settings.porttree.dbapi - - self._settings.settings.setcpv(self.get_cpv(), mydb = dbapi) - - def get_name(self): - return self._scpv[1] - - def get_version(self): - v = self._scpv[2] - if self._scpv[3] != "r0": - v += "-" + self._scpv[3] - return v - - def get_category(self): - return self._scpv[0] - - def is_installed(self): - return self._settings.vartree.dbapi.cpv_exists(self._cpv) - - def is_in_overlay(self): - ovl = self.get_overlay_path() - return ovl != self._settings.settings["PORTDIR"] and str(ovl) != "0" - - def get_overlay_path (self): - dir,ovl = self._settings.porttree.dbapi.findname2(self._cpv) - return ovl - - def is_in_system (self): - return (self._status != None) - - def is_missing_keyword(self): - return self._status and "missing keyword" in self._status - - def is_testing(self, use_keywords = True): - testArch = "~" + self.get_global_settings("ARCH") - if not use_keywords: # keywords are NOT taken into account - return testArch in self.get_package_settings("KEYWORDS").split() - - else: # keywords are taken into account - status = flags.new_testing_status(self.get_cpv()) - if status is None: # we haven't changed it in any way - return self._status and testArch+" keyword" in self._status - 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._status and ("profile" in self._status or "package.mask" in self._status) - - else: # we want the original portage value XXX: bug if masked by user AND by system - - # get the normal masked ones - if self._status and ("profile" in self._status or "package.mask" in self._status): - return not flags.is_locally_masked(self, changes = False) # assume that if it is locally masked, it is not masked by the system - else: # more difficult: get the ones we unmasked, but are masked by the system - try: - masked = self._settings.settings.pmaskdict[self.get_cp()] - except KeyError: # key error: not masked - return False - - for cpv in masked: - if self.matches(cpv): - return not flags.is_locally_masked(self, changes = False) # assume that if it is locally masked, it is not masked by the system - - return False - - def get_masking_reason(self): - reason = portage.getmaskingreason(self.get_cpv(), settings = self._settings.settings) - - if reason: - return reason.strip() - else: - return reason - - def get_iuse_flags (self, installed = False, removeForced = True): - if not self.is_in_system(): - installed = True - - iuse = flags.filter_defaults(self.get_package_settings("IUSE", installed = installed).split()) - - iuse = set(iuse) - - if removeForced: - return list(iuse.difference(self.forced_flags)) - else: - return list(iuse) - - def get_matched_dep_packages (self, depvar): - # change the useflags, because we have internally changed some, but not made them visible for portage - actual = self.get_actual_use_flags() - - depstring = "" - try: - for d in depvar: - depstring += self.get_package_settings(d, installed = False)+" " - except KeyError: # not found in porttree - use vartree - depstring = "" - for d in depvar: - depstring += self.get_package_settings(d, installed = True)+" " - - deps = portage.dep_check(depstring, None, self._settings.settings, myuse = actual, trees = self._trees) - - if not deps: # FIXME: what is the difference to [1, []] ? - return [] - - if deps[0] == 0: # error - raise DependencyCalcError, deps[1] - - deps = deps[1] - - return [d for d in deps if d[0] != "!"] - - def get_dep_packages (self, depvar = ["RDEPEND", "PDEPEND", "DEPEND"], with_criterions = False, return_blocks = False): - dep_pkgs = [] # the package list - - # change the useflags, because we have internally changed some, but not made them visible for portage - actual = self.get_actual_use_flags() - - depstring = "" - for d in depvar: - depstring += self.get_package_settings(d, installed = False)+" " - - # let portage do the main stuff ;) - # pay attention to any changes here - deps = portage.dep_check (depstring, self._settings.vartree.dbapi, self._settings.settings, myuse = actual, trees = self._trees) - - if not deps: # FIXME: what is the difference to [1, []] ? - return [] - - if deps[0] == 0: # error - raise DependencyCalcError, deps[1] - - deps = deps[1] - - def create_dep_pkgs_data (dep, pkg): - """Returns the data to enter into the dep_pkgs list, which is either the package cpv or a tuple - consisting of the cpv and the criterion.""" - if with_criterions: - return (pkg.get_cpv(), dep) - else: - return pkg.get_cpv() - - for dep in deps: - if dep[0] == '!': # blocking sth - blocked = system.find_packages(dep, system.SET_INSTALLED) - if len(blocked) == 1: # only exact one match allowed to be harmless - if blocked[0].get_slot_cp() == self.get_slot_cp(): # blocks in the same slot are harmless - continue - - if return_blocks: - if with_criterions: - dep_pkgs.append((dep, dep)) - else: - dep_pkgs.append(dep) - else: - raise BlockedException, (self.get_cpv(), blocked[0].get_cpv()) - - continue # finished with the blocking one -> next - - pkg = system.find_best_match(dep) - if not pkg: # try to find masked ones - pkgs = system.find_packages(dep, masked = True) - if not pkgs: - raise PackageNotFoundException, dep - - pkgs = system.sort_package_list(pkgs) - pkgs.reverse() - done = False - for p in pkgs: - if not p.is_masked(): - dep_pkgs.append(create_dep_pkgs_data(dep, p)) - done = True - break - if not done: - dep_pkgs.append(create_dep_pkgs_data(dep, pkgs[0])) - else: - dep_pkgs.append(create_dep_pkgs_data(dep, pkg)) - - return dep_pkgs - - def get_global_settings(self, key, installed = True): - with self._settingslock: - self._init_settings(installed) - v = self._settings.settings[key] - - return v - - def get_ebuild_path(self): - if self.is_in_system(): - return self._settings.porttree.dbapi.findname(self._cpv) - else: - return self._settings.vartree.dbapi.findname(self._cpv) - - def get_files (self): - if self.is_installed(): - path = os.path.join(self.get_global_settings("ROOT"), portage.VDB_PATH, self.get_cpv(), "CONTENTS") - with open(path) as f: - for line in f: - yield line.split()[1].strip() - - def get_package_settings(self, var, installed = True): - if installed and self.is_installed(): - mytree = self._settings.vartree - else: - mytree = self._settings.porttree - - r = mytree.dbapi.aux_get(self._cpv,[var]) - - return r[0] - - def get_installed_use_flags(self): - if self.is_installed(): - return self.get_package_settings("USE", installed = True).split() - else: return [] - - def compare_version(self,other): - v1 = self._scpv - v2 = portage.catpkgsplit(other.get_cpv()) - # if category is different - if v1[0] != v2[0]: - return cmp(v1[0],v2[0]) - # if name is different - elif v1[1] != v2[1]: - return cmp(v1[1],v2[1]) - # Compare versions - else: - return portage.pkgcmp(v1[1:],v2[1:]) - - def matches (self, criterion): - return system.cpv_matches(self.get_cpv(), criterion) + """This is a class abstracting a normal package which can be installed for the portage-system.""" + + def __init__ (self, cpv): + """Constructor. + + @param cpv: The cpv which describes the package to create. + @type cpv: string (cat/pkg-ver)""" + + Package.__init__(self, cpv) + self._scpv = system.split_cpv(self._cpv) + + if not self._scpv: + raise ValueError("invalid cpv: %s" % cpv) + + self._settings = system.settings + self._settingslock = system.settings.settingslock + self._settings_installed = None + + self._trees = system.settings.trees + + self.forced_flags = set() + + with self._settingslock: + self._init_settings(True) + self.forced_flags.update(self._settings.settings.usemask) + self.forced_flags.update(self._settings.settings.useforce) + + try: + self._status = portage.getmaskingstatus(self.get_cpv(), settings = self._settings.settings) + except KeyError: # package is not located in the system + self._status = None + + if self._status and len(self._status) == 1 and self._status[0] == "corrupted": + self._status = None + + def _init_settings (self, installed): + inst = (installed and self.is_installed()) or (self.is_installed() and not self.is_in_system()) + + if self._settings_installed is not None and self._settings_installed != inst: + self._settings.settings.reset() + + self._settings_installed = inst + + if inst: + dbapi = self._settings.vartree.dbapi + else: + dbapi = self._settings.porttree.dbapi + + self._settings.settings.setcpv(self.get_cpv(), mydb = dbapi) + + def get_name(self): + return self._scpv[1] + + def get_version(self): + v = self._scpv[2] + if self._scpv[3] != "r0": + v += "-" + self._scpv[3] + return v + + def get_category(self): + return self._scpv[0] + + def is_installed(self): + return self._settings.vartree.dbapi.cpv_exists(self._cpv) + + def is_in_overlay(self): + ovl = self.get_overlay_path() + return ovl != self._settings.settings["PORTDIR"] and str(ovl) != "0" + + def get_overlay_path (self): + dir,ovl = self._settings.porttree.dbapi.findname2(self._cpv) + return ovl + + def is_in_system (self): + return (self._status != None) + + def is_missing_keyword(self): + return self._status and "missing keyword" in self._status + + def is_testing(self, use_keywords = True): + testArch = "~" + self.get_global_settings("ARCH") + if not use_keywords: # keywords are NOT taken into account + return testArch in self.get_package_settings("KEYWORDS").split() + + else: # keywords are taken into account + status = flags.new_testing_status(self.get_cpv()) + if status is None: # we haven't changed it in any way + return self._status and testArch+" keyword" in self._status + 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._status and ("profile" in self._status or "package.mask" in self._status) + + else: # we want the original portage value XXX: bug if masked by user AND by system + + # get the normal masked ones + if self._status and ("profile" in self._status or "package.mask" in self._status): + return not flags.is_locally_masked(self, changes = False) # assume that if it is locally masked, it is not masked by the system + else: # more difficult: get the ones we unmasked, but are masked by the system + try: + masked = self._settings.settings.pmaskdict[self.get_cp()] + except KeyError: # key error: not masked + return False + + for cpv in masked: + if self.matches(cpv): + return not flags.is_locally_masked(self, changes = False) # assume that if it is locally masked, it is not masked by the system + + return False + + def get_masking_reason(self): + reason = portage.getmaskingreason(self.get_cpv(), settings = self._settings.settings) + + if reason: + return reason.strip() + else: + return reason + + def get_iuse_flags (self, installed = False, removeForced = True): + if not self.is_in_system(): + installed = True + + iuse = flags.filter_defaults(self.get_package_settings("IUSE", installed = installed).split()) + + iuse = set(iuse) + + if removeForced: + return list(iuse.difference(self.forced_flags)) + else: + return list(iuse) + + def get_matched_dep_packages (self, depvar): + # change the useflags, because we have internally changed some, but not made them visible for portage + actual = self.get_actual_use_flags() + + depstring = "" + try: + for d in depvar: + depstring += self.get_package_settings(d, installed = False)+" " + except KeyError: # not found in porttree - use vartree + depstring = "" + for d in depvar: + depstring += self.get_package_settings(d, installed = True)+" " + + deps = portage.dep_check(depstring, None, self._settings.settings, myuse = actual, trees = self._trees) + + if not deps: # FIXME: what is the difference to [1, []] ? + return [] + + if deps[0] == 0: # error + raise DependencyCalcError, deps[1] + + deps = deps[1] + + return [d for d in deps if d[0] != "!"] + + def get_dep_packages (self, depvar = ["RDEPEND", "PDEPEND", "DEPEND"], with_criterions = False, return_blocks = False): + dep_pkgs = [] # the package list + + # change the useflags, because we have internally changed some, but not made them visible for portage + actual = self.get_actual_use_flags() + + depstring = "" + for d in depvar: + depstring += self.get_package_settings(d, installed = False)+" " + + # let portage do the main stuff ;) + # pay attention to any changes here + deps = portage.dep_check (depstring, self._settings.vartree.dbapi, self._settings.settings, myuse = actual, trees = self._trees) + + if not deps: # FIXME: what is the difference to [1, []] ? + return [] + + if deps[0] == 0: # error + raise DependencyCalcError, deps[1] + + deps = deps[1] + + def create_dep_pkgs_data (dep, pkg): + """Returns the data to enter into the dep_pkgs list, which is either the package cpv or a tuple + consisting of the cpv and the criterion.""" + if with_criterions: + return (pkg.get_cpv(), dep) + else: + return pkg.get_cpv() + + for dep in deps: + if dep[0] == '!': # blocking sth + blocked = system.find_packages(dep, system.SET_INSTALLED) + if len(blocked) == 1: # only exact one match allowed to be harmless + if blocked[0].get_slot_cp() == self.get_slot_cp(): # blocks in the same slot are harmless + continue + + if return_blocks: + if with_criterions: + dep_pkgs.append((dep, dep)) + else: + dep_pkgs.append(dep) + else: + raise BlockedException, (self.get_cpv(), blocked[0].get_cpv()) + + continue # finished with the blocking one -> next + + pkg = system.find_best_match(dep) + if not pkg: # try to find masked ones + pkgs = system.find_packages(dep, masked = True) + if not pkgs: + raise PackageNotFoundException, dep + + pkgs = system.sort_package_list(pkgs) + pkgs.reverse() + done = False + for p in pkgs: + if not p.is_masked(): + dep_pkgs.append(create_dep_pkgs_data(dep, p)) + done = True + break + if not done: + dep_pkgs.append(create_dep_pkgs_data(dep, pkgs[0])) + else: + dep_pkgs.append(create_dep_pkgs_data(dep, pkg)) + + return dep_pkgs + + def get_global_settings(self, key, installed = True): + with self._settingslock: + self._init_settings(installed) + v = self._settings.settings[key] + + return v + + def get_ebuild_path(self): + if self.is_in_system(): + return self._settings.porttree.dbapi.findname(self._cpv) + else: + return self._settings.vartree.dbapi.findname(self._cpv) + + def get_files (self): + if self.is_installed(): + path = os.path.join(self.get_global_settings("ROOT"), portage.VDB_PATH, self.get_cpv(), "CONTENTS") + with open(path) as f: + for line in f: + yield line.split()[1].strip() + + def get_package_settings(self, var, installed = True): + if installed and self.is_installed(): + mytree = self._settings.vartree + else: + mytree = self._settings.porttree + + r = mytree.dbapi.aux_get(self._cpv,[var]) + + return r[0] + + def get_installed_use_flags(self): + if self.is_installed(): + return self.get_package_settings("USE", installed = True).split() + else: return [] + + def compare_version(self,other): + v1 = self._scpv + v2 = portage.catpkgsplit(other.get_cpv()) + # if category is different + if v1[0] != v2[0]: + return cmp(v1[0],v2[0]) + # if name is different + elif v1[1] != v2[1]: + return cmp(v1[1],v2[1]) + # Compare versions + else: + return portage.pkgcmp(v1[1:],v2[1:]) + + def matches (self, criterion): + return system.cpv_matches(self.get_cpv(), criterion) diff --git a/portato/backend/portage/package_22.py b/portato/backend/portage/package_22.py index 4fe03d9..1dae1fe 100644 --- a/portato/backend/portage/package_22.py +++ b/portato/backend/portage/package_22.py @@ -15,8 +15,8 @@ from __future__ import absolute_import, with_statement from .package import PortagePackage class PortagePackage_22 (PortagePackage): - """ - The 2.2 specialization of the portage package. - Currently this is identical to the normal one. - """ - pass + """ + The 2.2 specialization of the portage package. + Currently this is identical to the normal one. + """ + pass diff --git a/portato/backend/portage/sets.py b/portato/backend/portage/sets.py index 24ceaca..8d8ef28 100644 --- a/portato/backend/portage/sets.py +++ b/portato/backend/portage/sets.py @@ -17,130 +17,130 @@ import itertools as itt import portage try: - import portage.dep as portage_dep + import portage.dep as portage_dep except ImportError: - import portage_dep + import portage_dep from .. import system from ...helper import debug class Set(object): - def get_pkgs(self, key, is_regexp, masked, with_version, only_cpv): - raise NotImplementedError + def get_pkgs(self, key, is_regexp, masked, with_version, only_cpv): + raise NotImplementedError - def find (self, key, masked = False, with_version = True, only_cpv = False): - if key is None: key = "" - - is_regexp = key == "" or ("*" in key and key[0] not in ("*","=","<",">","~","!")) + def find (self, key, masked = False, with_version = True, only_cpv = False): + if key is None: key = "" + + is_regexp = key == "" or ("*" in key and key[0] not in ("*","=","<",">","~","!")) - try: - t = self.get_pkgs(key, is_regexp, masked, with_version, only_cpv) - # catch the "ambigous package" Exception - except ValueError, e: - if isinstance(e[0], list): - t = set() - for cp in e[0]: - t.update(self.get_pkgs(cp, is_regexp, masked, with_version, only_cpv)) - else: - raise + try: + t = self.get_pkgs(key, is_regexp, masked, with_version, only_cpv) + # catch the "ambigous package" Exception + except ValueError, e: + if isinstance(e[0], list): + t = set() + for cp in e[0]: + t.update(self.get_pkgs(cp, is_regexp, masked, with_version, only_cpv)) + else: + raise - return t + return t class FilterSet (Set): - def get_list(self): - raise NotImplementedError - - def get_pkgs (self, key, is_regexp, masked, with_version, only_cpv): - t = set() - for pkg in self.get_list(): - if is_regexp: - if not re.match(key, pkg, re.I): continue + def get_list(self): + raise NotImplementedError + + def get_pkgs (self, key, is_regexp, masked, with_version, only_cpv): + t = set() + for pkg in self.get_list(): + if is_regexp: + if not re.match(key, pkg, re.I): continue - if not with_version: - t.add(portage_dep.dep_getkey(pkg)) - - t.add(system.find_best_match(pkg, only_cpv = True)) + if not with_version: + t.add(portage_dep.dep_getkey(pkg)) + + t.add(system.find_best_match(pkg, only_cpv = True)) - return t + return t class PortageSet (FilterSet): - def __init__ (self, name): - FilterSet.__init__(self) - debug("Loading portage set '%s'", name) - self.portageSet = system.settings.setsconfig.getSets()[name] + def __init__ (self, name): + FilterSet.__init__(self) + debug("Loading portage set '%s'", name) + self.portageSet = system.settings.setsconfig.getSets()[name] - def get_list(self): - return itt.imap(str, self.portageSet.getAtoms()) + def get_list(self): + return itt.imap(str, self.portageSet.getAtoms()) class SystemSet (FilterSet): - def get_list(self): - for cp in system.settings.settings.packages: - if cp[0] == "*": yield cp[1:] + def get_list(self): + for cp in system.settings.settings.packages: + if cp[0] == "*": yield cp[1:] class WorldSet (FilterSet): - def get_list(self): - with open(portage.WORLD_FILE) as f: - for cp in f: - cp = cp.strip() - if cp and cp[0] != "#": - yield cp + def get_list(self): + with open(portage.WORLD_FILE) as f: + for cp in f: + cp = cp.strip() + if cp and cp[0] != "#": + yield cp class InstalledSet (Set): - def get_pkgs (self, key, is_regexp, masked, with_version, only_cpv): - if is_regexp: - if with_version: - t = system.settings.vartree.dbapi.cpv_all() - else: - t = system.settings.vartree.dbapi.cp_all() + def get_pkgs (self, key, is_regexp, masked, with_version, only_cpv): + if is_regexp: + if with_version: + t = system.settings.vartree.dbapi.cpv_all() + else: + t = system.settings.vartree.dbapi.cp_all() - if key: - t = filter(lambda x: re.match(key, x, re.I), t) + if key: + t = filter(lambda x: re.match(key, x, re.I), t) - return set(t) - else: - return set(system.settings.vartree.dbapi.match(key)) + return set(t) + else: + return set(system.settings.vartree.dbapi.match(key)) class TreeSet (Set): - def get_pkgs (self, key, is_regexp, masked, with_version, only_cpv): - if is_regexp: - if with_version: - t = system.settings.porttree.dbapi.cpv_all() - else: - t = system.settings.porttree.dbapi.cp_all() + def get_pkgs (self, key, is_regexp, masked, with_version, only_cpv): + if is_regexp: + if with_version: + t = system.settings.porttree.dbapi.cpv_all() + else: + t = system.settings.porttree.dbapi.cp_all() - if key: - t = filter(lambda x: re.match(key, x, re.I), t) + if key: + t = filter(lambda x: re.match(key, x, re.I), t) - elif masked: - t = system.settings.porttree.dbapi.xmatch("match-all", key) - else: - t = system.settings.porttree.dbapi.match(key) + elif masked: + t = system.settings.porttree.dbapi.xmatch("match-all", key) + else: + t = system.settings.porttree.dbapi.match(key) - return set(t) + return set(t) class AllSet (Set): - - def __init__ (self): - Set.__init__(self) - self.tree = TreeSet() - self.installed = InstalledSet() + + def __init__ (self): + Set.__init__(self) + self.tree = TreeSet() + self.installed = InstalledSet() - def find (self, *args, **kwargs): - return self.tree.find(*args, **kwargs) | self.installed.find(*args, **kwargs) + def find (self, *args, **kwargs): + return self.tree.find(*args, **kwargs) | self.installed.find(*args, **kwargs) class UninstalledSet (Set): - def __init__ (self): - Set.__init__(self) - self.all = AllSet() - self.installed = InstalledSet() + def __init__ (self): + Set.__init__(self) + self.all = AllSet() + self.installed = InstalledSet() - def find (self, *args, **kwargs): - return self.all.find(*args, **kwargs) - self.installed.find(*args, **kwargs) + def find (self, *args, **kwargs): + return self.all.find(*args, **kwargs) - self.installed.find(*args, **kwargs) diff --git a/portato/backend/portage/settings.py b/portato/backend/portage/settings.py index 5d466ad..0e7eccb 100644 --- a/portato/backend/portage/settings.py +++ b/portato/backend/portage/settings.py @@ -17,42 +17,42 @@ import portage from threading import Lock class PortageSettings: - """Encapsulation of the portage settings. - - @ivar settings: portage settings - @ivar settingslock: a simple Lock - @ivar trees: a dictionary of the trees - @ivar porttree: shortcut to C{trees[root]["porttree"]} - @ivar vartree: shortcut to C{trees[root]["vartree"]} - @ivar virtuals: shortcut to C{trees[root]["virtuals"]}""" - - def __init__ (self): - """Initializes the instance. Calls L{load()}.""" - self.settingslock = Lock() - self.trees = None - self.load() - - def load(self): - """(Re)loads the portage settings and sets the variables.""" - - kwargs = {} - for k, envvar in (("config_root", "PORTAGE_CONFIGROOT"), ("target_root", "ROOT")): - kwargs[k] = os.environ.get(envvar, None) - self.trees = portage.create_trees(trees=self.trees, **kwargs) - - self.settings = self.trees["/"]["vartree"].settings - - for myroot in self.trees: - if myroot != "/": - self.settings = self.trees[myroot]["vartree"].settings - break - - self.settings.unlock() - - root = self.settings["ROOT"] - - self.porttree = self.trees[root]["porttree"] - self.vartree = self.trees[root]["vartree"] - self.virtuals = self.trees[root]["virtuals"] - - portage.settings = None # we use our own one ... + """Encapsulation of the portage settings. + + @ivar settings: portage settings + @ivar settingslock: a simple Lock + @ivar trees: a dictionary of the trees + @ivar porttree: shortcut to C{trees[root]["porttree"]} + @ivar vartree: shortcut to C{trees[root]["vartree"]} + @ivar virtuals: shortcut to C{trees[root]["virtuals"]}""" + + def __init__ (self): + """Initializes the instance. Calls L{load()}.""" + self.settingslock = Lock() + self.trees = None + self.load() + + def load(self): + """(Re)loads the portage settings and sets the variables.""" + + kwargs = {} + for k, envvar in (("config_root", "PORTAGE_CONFIGROOT"), ("target_root", "ROOT")): + kwargs[k] = os.environ.get(envvar, None) + self.trees = portage.create_trees(trees=self.trees, **kwargs) + + self.settings = self.trees["/"]["vartree"].settings + + for myroot in self.trees: + if myroot != "/": + self.settings = self.trees[myroot]["vartree"].settings + break + + self.settings.unlock() + + root = self.settings["ROOT"] + + self.porttree = self.trees[root]["porttree"] + self.vartree = self.trees[root]["vartree"] + self.virtuals = self.trees[root]["virtuals"] + + portage.settings = None # we use our own one ... diff --git a/portato/backend/portage/settings_22.py b/portato/backend/portage/settings_22.py index a43d69e..5274b3b 100644 --- a/portato/backend/portage/settings_22.py +++ b/portato/backend/portage/settings_22.py @@ -16,12 +16,12 @@ import portage.sets from .settings import PortageSettings class PortageSettings_22 (PortageSettings): - """Enhances the normal PortageSettings in ways, that it adds the setsconfig.""" + """Enhances the normal PortageSettings in ways, that it adds the setsconfig.""" - def __init__ (self): - PortageSettings.__init__(self) + def __init__ (self): + PortageSettings.__init__(self) - def load (self): - PortageSettings.load(self) + def load (self): + PortageSettings.load(self) - self.setsconfig = portage.sets.load_default_config(self.settings, self.trees[self.settings["ROOT"]]) + self.setsconfig = portage.sets.load_default_config(self.settings, self.trees[self.settings["ROOT"]]) diff --git a/portato/backend/portage/system.py b/portato/backend/portage/system.py index 2394a10..e81951e 100644 --- a/portato/backend/portage/system.py +++ b/portato/backend/portage/system.py @@ -15,9 +15,9 @@ from __future__ import absolute_import, with_statement import re, os, os.path import portage try: - import portage.dep as portage_dep + import portage.dep as portage_dep except ImportError: - import portage_dep + import portage_dep from collections import defaultdict import itertools as itt @@ -30,386 +30,386 @@ from ..system_interface import SystemInterface from ...helper import debug, info, warning, unique_array class PortageSystem (SystemInterface): - """This class provides access to the portage-system.""" - - # pre-compile the RE removing the ".svn" and "CVS" entries - unwantedPkgsRE = re.compile(r".*(\.svn|CVS)$") - withBdepsRE = re.compile(r"--with-bdeps\s*( |=)\s*y") + """This class provides access to the portage-system.""" + + # pre-compile the RE removing the ".svn" and "CVS" entries + unwantedPkgsRE = re.compile(r".*(\.svn|CVS)$") + withBdepsRE = re.compile(r"--with-bdeps\s*( |=)\s*y") - def __init__ (self): - """Constructor.""" - self.settings = PortageSettings() - portage.WORLD_FILE = os.path.join(self.settings.settings["ROOT"],portage.WORLD_FILE) - - self.use_descs = {} - self.local_use_descs = defaultdict(dict) - - self.setmap = { - self.SET_ALL : syssets.AllSet, - self.SET_INSTALLED : syssets.InstalledSet, - self.SET_UNINSTALLED : syssets.UninstalledSet, - self.SET_TREE : syssets.TreeSet, - "world" : syssets.WorldSet, - "system" : syssets.SystemSet - } - - def has_set_support (self): - return False - - def get_sets (self, description = False): - if description: - return (("world", "The world set."), ("system", "The system set.")) - else: - return ("world", "system") - - def get_version (self): - return "Portage %s" % portage.VERSION - - def new_package (self, cpv): - return PortagePackage(cpv) - - def get_config_path (self): - return portage.USER_CONFIG_PATH - - def get_merge_command (self): - return ["/usr/bin/python", "/usr/bin/emerge"] - - def get_sync_command (self): - return self.get_merge_command()+["--sync"] - - def get_oneshot_option (self): - return ["--oneshot"] - - def get_newuse_option (self): - return ["--newuse"] - - def get_deep_option (self): - return ["--deep"] - - def get_update_option (self): - return ["--update"] - - def get_pretend_option (self): - return ["--pretend", "--verbose"] - - def get_unmerge_option (self): - return ["--unmerge"] - - def get_environment (self): - default_opts = self.get_global_settings("EMERGE_DEFAULT_OPTS") - opts = dict(os.environ) - opts.update(TERM = "xterm") # emulate terminal :) - opts.update(PAGER = "less") # force less - - 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)) - - return opts - - def cpv_matches (self, cpv, criterion): - if portage.match_from_list(criterion, [cpv]) == []: - return False - else: - return True - - def with_bdeps(self): - """Returns whether the "--with-bdeps" option is set to true. - - @returns: the value of --with-bdeps - @rtype: boolean - """ - - settings = self.get_global_settings("EMERGE_DEFAULT_OPTS").split() - for s in settings: - if self.withBdepsRE.match(s): - return True - - return False - - def find_lambda (self, name): - """Returns the function needed by all the find_all_*-functions. Returns None if no name is given. - - @param name: name to build the function of - @type name: string or RE - @returns: - 1. None if no name is given - 2. a lambda function - @rtype: function - """ - - if name != None: - if isinstance(name, str): - return lambda x: re.match(".*"+name+".*",x, re.I) - else: # assume regular expression - return lambda x: name.match(x) - else: - return lambda x: True - - 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 [self.new_package(x) for x in list_of_packages] - elif not isinstance(list_of_packages, list): - return list(list_of_packages) - else: - return list_of_packages - - def get_global_settings (self, key): - self.settings.settings.reset() - return self.settings.settings[key] - - def find_best (self, list, only_cpv = False): - if only_cpv: - return portage.best(list) - else: - return self.new_package(portage.best(list)) - - def find_best_match (self, search_key, masked = False, only_installed = False, only_cpv = False): - t = [] - - if not only_installed: - pkgSet = self.SET_TREE - else: - pkgSet = self.SET_INSTALLED - - t = self.find_packages(search_key, pkgSet = pkgSet, masked = masked, with_version = True, only_cpv = True) - - 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())] - 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) - return self.find_best(t, only_cpv) - - return None - - def _get_set (self, pkgSet): - pkgSet = pkgSet.lower() - if pkgSet == "": pkgSet = self.SET_ALL - - return self.setmap[pkgSet]() - - def find_packages (self, key = "", pkgSet = SystemInterface.SET_ALL, masked = False, with_version = True, only_cpv = False): - return self.geneticize_list(self._get_set(pkgSet).find(key, masked, with_version, only_cpv), only_cpv or not with_version) - - def list_categories (self, name = None): - categories = self.settings.settings.categories - return filter(self.find_lambda(name), categories) - - def split_cpv (self, cpv): - cpv = portage.dep_getcpv(cpv) - return portage.catpkgsplit(cpv) - - def sort_package_list(self, pkglist): - pkglist.sort(PortagePackage.compare_version) # XXX: waaah ... direct package naming... =/ - return pkglist - - def reload_settings (self): - self.settings.load() - - def get_new_packages (self, packages): - """Gets a list of packages and returns the best choice for each in the portage tree. - - @param packages: the list of packages - @type packages: string[] - @returns: the list of packages - @rtype: backend.Package[] - """ - - 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) - - best_p = self.find_best_match(p) - if best_p is None: - best_p = self.find_best_match(p, masked = True) - if best_p is None: - warning(_("No best match for %s. It seems not to be in the tree anymore.") % p) - continue - else: - debug("Best match for %s is masked" % p) - - if len(inst) > 1: - myslots = set() - for i in inst: # get the slots of the installed packages - myslots.add(i.get_package_settings("SLOT")) - - myslots.add(best_p.get_package_settings("SLOT")) # add the slot of the best package in portage - for slot in myslots: - crit = "%s:%s" % (p, slot) - append(crit, self.find_best_match(crit), inst) - else: - append(p, best_p, inst) - - return new_packages - - def get_updated_packages (self): - packages = self.get_new_packages(self.find_packages(pkgSet = self.SET_INSTALLED, with_version = False)) - packages = [x for x in packages if x is not None and not x.is_installed()] - return packages - - def update_world (self, sets = ("world", "system"), newuse = False, deep = False): - packages = set() - map(packages.add, itt.chain(*[self.find_packages(pkgSet = s, with_version = False) for s in sets])) - - states = [(["RDEPEND", "PDEPEND"], True)] - if self.with_bdeps(): - states.append((["DEPEND"], True)) - - checked = [] - updating = [] - raw_checked = {} - def check (p, add_not_installed = True, prev_appended = False): - """Checks whether a package is updated or not.""" - - if p.get_slot_cp() in checked: - return - else: - if (not p.is_installed()) and (not add_not_installed): - # don't add these packages to checked as we may see them again - # - and then we might have add_not_installed being True - return - else: - checked.append(p.get_slot_cp()) - - appended = False - tempDeep = False - - if not p.is_installed(): - oldList = self.find_packages(p.get_slot_cp(), self.SET_INSTALLED) - if oldList: - old = oldList[0] # we should only have one package here - else it is a bug - else: - oldList = self.sort_package_list(self.find_packages(p.get_cp(), self.SET_INSTALLED)) - if not oldList: - info(_("Found a not installed dependency: %s.") % p.get_cpv()) - oldList = [p] - - old = oldList[-1] - - updating.append((p, old)) - appended = True - p = old - - if newuse and p.is_installed() and p.is_in_system(): # there is no use to check newuse for a package which is not existing in portage anymore :) - - new_iuse = set(p.get_iuse_flags(installed = False)) # IUSE in the ebuild - old_iuse = set(p.get_iuse_flags(installed = True)) # IUSE in the vardb - - # add forced flags, as they might trigger a rebuild - new_iuse_f = set(p.get_iuse_flags(installed = False, removeForced = False)) - old_iuse_f = set(p.get_iuse_flags(installed = True, removeForced = False)) - - if new_iuse.symmetric_difference(old_iuse): # difference between IUSE (w/o forced) - tempDeep = True - if not appended: - updating.append((p,p)) - appended = True - - else: # check for difference between the _set_ useflags (w/ forced) - if new_iuse_f.intersection(p.get_actual_use_flags()).symmetric_difference(old_iuse_f.intersection(p.get_installed_use_flags())): - tempDeep = True - if not appended: - updating.append((p,p)) - appended = True - - if deep or tempDeep: - if (appended or prev_appended) and len(states) < 2: - real_states = states + [("PDEPEND", True), ("DEPEND", False)] - else: - real_states = states - for state in real_states: - for i in p.get_matched_dep_packages(state[0]): - if i not in raw_checked or raw_checked[i] == False: - raw_checked.update({i : state[1]}) - bm = self.get_new_packages([i]) - if not bm: - warning(_("Bug? No best match could be found for '%(package)s'. Needed by: '%(cpv)s'."), {"package" : i, "cpv": p.get_cpv()}) - else: - for pkg in bm: - if not pkg: continue - check(pkg, state[1], appended) # XXX: should be 'or'ed with prev_appended? - - for p in self.get_new_packages(packages): - if not p: continue # if a masked package is installed we have "None" here - check(p, True) - - return updating - - def get_use_desc (self, flag, package = None): - # In the first run the dictionaries 'use_descs' and 'local_use_descs' are filled. - - # fill cache if needed - if not self.use_descs and not self.local_use_descs: - for dir in [self.settings.settings["PORTDIR"]] + self.settings.settings["PORTDIR_OVERLAY"].split(): - - # read use.desc - try: - f = open(os.path.join(dir, "profiles/use.desc")) - for line in f: - line = line.strip() - if line and line[0] != '#': - fields = [x.strip() for x in line.split(" - ",1)] - if len(fields) == 2: - self.use_descs[fields[0]] = fields[1] - except IOError: - pass - finally: - f.close() - - # read use.local.desc - try: - f = open(os.path.join(dir, "profiles/use.local.desc")) - for line in f: - line = line.strip() - if line and line[0] != '#': - fields = [x.strip() for x in line.split(":",1)] - if len(fields) == 2: - subfields = [x.strip() for x in fields[1].split(" - ",1)] - if len(subfields) == 2: - self.local_use_descs[fields[0]].update([subfields]) - except IOError: - pass - finally: - f.close() - - # start - desc = self.use_descs.get(flag, "") - if package is not None: - if package in self.local_use_descs: - desc = self.local_use_descs[package].get(flag, desc) - - return desc + def __init__ (self): + """Constructor.""" + self.settings = PortageSettings() + portage.WORLD_FILE = os.path.join(self.settings.settings["ROOT"],portage.WORLD_FILE) + + self.use_descs = {} + self.local_use_descs = defaultdict(dict) + + self.setmap = { + self.SET_ALL : syssets.AllSet, + self.SET_INSTALLED : syssets.InstalledSet, + self.SET_UNINSTALLED : syssets.UninstalledSet, + self.SET_TREE : syssets.TreeSet, + "world" : syssets.WorldSet, + "system" : syssets.SystemSet + } + + def has_set_support (self): + return False + + def get_sets (self, description = False): + if description: + return (("world", "The world set."), ("system", "The system set.")) + else: + return ("world", "system") + + def get_version (self): + return "Portage %s" % portage.VERSION + + def new_package (self, cpv): + return PortagePackage(cpv) + + def get_config_path (self): + return portage.USER_CONFIG_PATH + + def get_merge_command (self): + return ["/usr/bin/python", "/usr/bin/emerge"] + + def get_sync_command (self): + return self.get_merge_command()+["--sync"] + + def get_oneshot_option (self): + return ["--oneshot"] + + def get_newuse_option (self): + return ["--newuse"] + + def get_deep_option (self): + return ["--deep"] + + def get_update_option (self): + return ["--update"] + + def get_pretend_option (self): + return ["--pretend", "--verbose"] + + def get_unmerge_option (self): + return ["--unmerge"] + + def get_environment (self): + default_opts = self.get_global_settings("EMERGE_DEFAULT_OPTS") + opts = dict(os.environ) + opts.update(TERM = "xterm") # emulate terminal :) + opts.update(PAGER = "less") # force less + + 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)) + + return opts + + def cpv_matches (self, cpv, criterion): + if portage.match_from_list(criterion, [cpv]) == []: + return False + else: + return True + + def with_bdeps(self): + """Returns whether the "--with-bdeps" option is set to true. + + @returns: the value of --with-bdeps + @rtype: boolean + """ + + settings = self.get_global_settings("EMERGE_DEFAULT_OPTS").split() + for s in settings: + if self.withBdepsRE.match(s): + return True + + return False + + def find_lambda (self, name): + """Returns the function needed by all the find_all_*-functions. Returns None if no name is given. + + @param name: name to build the function of + @type name: string or RE + @returns: + 1. None if no name is given + 2. a lambda function + @rtype: function + """ + + if name != None: + if isinstance(name, str): + return lambda x: re.match(".*"+name+".*",x, re.I) + else: # assume regular expression + return lambda x: name.match(x) + else: + return lambda x: True + + 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 [self.new_package(x) for x in list_of_packages] + elif not isinstance(list_of_packages, list): + return list(list_of_packages) + else: + return list_of_packages + + def get_global_settings (self, key): + self.settings.settings.reset() + return self.settings.settings[key] + + def find_best (self, list, only_cpv = False): + if only_cpv: + return portage.best(list) + else: + return self.new_package(portage.best(list)) + + def find_best_match (self, search_key, masked = False, only_installed = False, only_cpv = False): + t = [] + + if not only_installed: + pkgSet = self.SET_TREE + else: + pkgSet = self.SET_INSTALLED + + t = self.find_packages(search_key, pkgSet = pkgSet, masked = masked, with_version = True, only_cpv = True) + + 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())] + 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) + return self.find_best(t, only_cpv) + + return None + + def _get_set (self, pkgSet): + pkgSet = pkgSet.lower() + if pkgSet == "": pkgSet = self.SET_ALL + + return self.setmap[pkgSet]() + + def find_packages (self, key = "", pkgSet = SystemInterface.SET_ALL, masked = False, with_version = True, only_cpv = False): + return self.geneticize_list(self._get_set(pkgSet).find(key, masked, with_version, only_cpv), only_cpv or not with_version) + + def list_categories (self, name = None): + categories = self.settings.settings.categories + return filter(self.find_lambda(name), categories) + + def split_cpv (self, cpv): + cpv = portage.dep_getcpv(cpv) + return portage.catpkgsplit(cpv) + + def sort_package_list(self, pkglist): + pkglist.sort(PortagePackage.compare_version) # XXX: waaah ... direct package naming... =/ + return pkglist + + def reload_settings (self): + self.settings.load() + + def get_new_packages (self, packages): + """Gets a list of packages and returns the best choice for each in the portage tree. + + @param packages: the list of packages + @type packages: string[] + @returns: the list of packages + @rtype: backend.Package[] + """ + + 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) + + best_p = self.find_best_match(p) + if best_p is None: + best_p = self.find_best_match(p, masked = True) + if best_p is None: + warning(_("No best match for %s. It seems not to be in the tree anymore.") % p) + continue + else: + debug("Best match for %s is masked" % p) + + if len(inst) > 1: + myslots = set() + for i in inst: # get the slots of the installed packages + myslots.add(i.get_package_settings("SLOT")) + + myslots.add(best_p.get_package_settings("SLOT")) # add the slot of the best package in portage + for slot in myslots: + crit = "%s:%s" % (p, slot) + append(crit, self.find_best_match(crit), inst) + else: + append(p, best_p, inst) + + return new_packages + + def get_updated_packages (self): + packages = self.get_new_packages(self.find_packages(pkgSet = self.SET_INSTALLED, with_version = False)) + packages = [x for x in packages if x is not None and not x.is_installed()] + return packages + + def update_world (self, sets = ("world", "system"), newuse = False, deep = False): + packages = set() + map(packages.add, itt.chain(*[self.find_packages(pkgSet = s, with_version = False) for s in sets])) + + states = [(["RDEPEND", "PDEPEND"], True)] + if self.with_bdeps(): + states.append((["DEPEND"], True)) + + checked = [] + updating = [] + raw_checked = {} + def check (p, add_not_installed = True, prev_appended = False): + """Checks whether a package is updated or not.""" + + if p.get_slot_cp() in checked: + return + else: + if (not p.is_installed()) and (not add_not_installed): + # don't add these packages to checked as we may see them again + # - and then we might have add_not_installed being True + return + else: + checked.append(p.get_slot_cp()) + + appended = False + tempDeep = False + + if not p.is_installed(): + oldList = self.find_packages(p.get_slot_cp(), self.SET_INSTALLED) + if oldList: + old = oldList[0] # we should only have one package here - else it is a bug + else: + oldList = self.sort_package_list(self.find_packages(p.get_cp(), self.SET_INSTALLED)) + if not oldList: + info(_("Found a not installed dependency: %s.") % p.get_cpv()) + oldList = [p] + + old = oldList[-1] + + updating.append((p, old)) + appended = True + p = old + + if newuse and p.is_installed() and p.is_in_system(): # there is no use to check newuse for a package which is not existing in portage anymore :) + + new_iuse = set(p.get_iuse_flags(installed = False)) # IUSE in the ebuild + old_iuse = set(p.get_iuse_flags(installed = True)) # IUSE in the vardb + + # add forced flags, as they might trigger a rebuild + new_iuse_f = set(p.get_iuse_flags(installed = False, removeForced = False)) + old_iuse_f = set(p.get_iuse_flags(installed = True, removeForced = False)) + + if new_iuse.symmetric_difference(old_iuse): # difference between IUSE (w/o forced) + tempDeep = True + if not appended: + updating.append((p,p)) + appended = True + + else: # check for difference between the _set_ useflags (w/ forced) + if new_iuse_f.intersection(p.get_actual_use_flags()).symmetric_difference(old_iuse_f.intersection(p.get_installed_use_flags())): + tempDeep = True + if not appended: + updating.append((p,p)) + appended = True + + if deep or tempDeep: + if (appended or prev_appended) and len(states) < 2: + real_states = states + [("PDEPEND", True), ("DEPEND", False)] + else: + real_states = states + for state in real_states: + for i in p.get_matched_dep_packages(state[0]): + if i not in raw_checked or raw_checked[i] == False: + raw_checked.update({i : state[1]}) + bm = self.get_new_packages([i]) + if not bm: + warning(_("Bug? No best match could be found for '%(package)s'. Needed by: '%(cpv)s'."), {"package" : i, "cpv": p.get_cpv()}) + else: + for pkg in bm: + if not pkg: continue + check(pkg, state[1], appended) # XXX: should be 'or'ed with prev_appended? + + for p in self.get_new_packages(packages): + if not p: continue # if a masked package is installed we have "None" here + check(p, True) + + return updating + + def get_use_desc (self, flag, package = None): + # In the first run the dictionaries 'use_descs' and 'local_use_descs' are filled. + + # fill cache if needed + if not self.use_descs and not self.local_use_descs: + for dir in [self.settings.settings["PORTDIR"]] + self.settings.settings["PORTDIR_OVERLAY"].split(): + + # read use.desc + try: + f = open(os.path.join(dir, "profiles/use.desc")) + for line in f: + line = line.strip() + if line and line[0] != '#': + fields = [x.strip() for x in line.split(" - ",1)] + if len(fields) == 2: + self.use_descs[fields[0]] = fields[1] + except IOError: + pass + finally: + f.close() + + # read use.local.desc + try: + f = open(os.path.join(dir, "profiles/use.local.desc")) + for line in f: + line = line.strip() + if line and line[0] != '#': + fields = [x.strip() for x in line.split(":",1)] + if len(fields) == 2: + subfields = [x.strip() for x in fields[1].split(" - ",1)] + if len(subfields) == 2: + self.local_use_descs[fields[0]].update([subfields]) + except IOError: + pass + finally: + f.close() + + # start + desc = self.use_descs.get(flag, "") + if package is not None: + if package in self.local_use_descs: + desc = self.local_use_descs[package].get(flag, desc) + + return desc diff --git a/portato/backend/portage/system_22.py b/portato/backend/portage/system_22.py index a329ab0..d5b605f 100644 --- a/portato/backend/portage/system_22.py +++ b/portato/backend/portage/system_22.py @@ -24,41 +24,41 @@ from . import sets as syssets class PortageSystem_22 (PortageSystem): - def __init__ (self): - self.settings = PortageSettings_22() - portage.WORLD_FILE = os.path.join(self.settings.settings["ROOT"],portage.WORLD_FILE) + def __init__ (self): + self.settings = PortageSettings_22() + portage.WORLD_FILE = os.path.join(self.settings.settings["ROOT"],portage.WORLD_FILE) - self.use_descs = {} - self.local_use_descs = defaultdict(dict) + self.use_descs = {} + self.local_use_descs = defaultdict(dict) - self.setmap = { - self.SET_ALL : syssets.AllSet, - self.SET_INSTALLED : syssets.InstalledSet, - self.SET_UNINSTALLED : syssets.UninstalledSet, - self.SET_TREE : syssets.TreeSet - } + self.setmap = { + self.SET_ALL : syssets.AllSet, + self.SET_INSTALLED : syssets.InstalledSet, + self.SET_UNINSTALLED : syssets.UninstalledSet, + self.SET_TREE : syssets.TreeSet + } - def get_update_option (self): - return ["--update", "--oneshot"] # --oneshot to not record the used sets in world file + def get_update_option (self): + return ["--update", "--oneshot"] # --oneshot to not record the used sets in world file - def has_set_support (self): - return True + def has_set_support (self): + return True - def get_sets (self, description = False): - if description: - return ((name, set.description) for name, set in self.settings.setsconfig.getSets().iteritems()) - else: - return tuple(self.settings.setsconfig.getSets()) + def get_sets (self, description = False): + if description: + return ((name, set.description) for name, set in self.settings.setsconfig.getSets().iteritems()) + else: + return tuple(self.settings.setsconfig.getSets()) - def _get_set (self, pkgSet): - pkgSet = pkgSet.lower() - if pkgSet == "": pkgSet = self.SET_ALL + def _get_set (self, pkgSet): + pkgSet = pkgSet.lower() + if pkgSet == "": pkgSet = self.SET_ALL - s = self.setmap.get(pkgSet, None) - if s is None: - return syssets.PortageSet(pkgSet) - else: - return s() + s = self.setmap.get(pkgSet, None) + if s is None: + return syssets.PortageSet(pkgSet) + else: + return s() - def new_package (self, cpv): - return PortagePackage_22(cpv) + def new_package (self, cpv): + return PortagePackage_22(cpv) diff --git a/portato/backend/system_interface.py b/portato/backend/system_interface.py index 1cb0ed1..6f13042 100644 --- a/portato/backend/system_interface.py +++ b/portato/backend/system_interface.py @@ -11,282 +11,282 @@ # Written by René 'Necoro' Neumann class SystemInterface (object): - - SET_ALL = "__portato_all__" - SET_TREE = "__portato_tree__" - SET_INSTALLED = "__portato_installed__" - SET_UNINSTALLED = "__portato_uninstalled__" - - def has_set_support (self): - """Signals, whether this backend supports sets. - - @rtype: boolean - """ - raise NotImplementedError - - def get_sets (self): - """Returns all supported sets in tuples consisting of name and description. - If sets aren't supported, at least "world" and "system" have to be returned. - - @rtype: iter(string, string) - """ - raise NotImplementedError - - def get_version (self): - """Returns the version of the used backend. - - @rtype: string - """ - raise NotImplementedError - - def split_cpv (self, cpv): - """Splits a cpv into all its parts. - - @param cpv: the cpv to split - @type cpv: string - @returns: the splitted cpv - @rtype: string[] - """ - - raise NotImplementedError - - def cpv_matches (self, cpv, criterion): - """Checks whether a cpv matches a specific criterion. - - @param cpv: cpv to check - @type cpv: string - @param criterion: criterion to check against - @type criterion: string - @returns: match result - @rtype: boolean - """ - - raise NotImplementedError - - def find_best(self, list, only_cpv = False): - """Returns the best package out of a list of packages. - - @param list: the list of packages to select from - @type list: string[] - @param only_cpv: do not return package but only the cpv - @type only_cpv: boolean - - @returns: the best package - @rtype: backend.Package or string - """ - - raise NotImplementedError - - def find_best_match (self, search_key, masked = False, only_installed = False, only_cpv = False): - """Finds the best match in the portage tree. It does not find masked packages! - - @param search_key: the key to find in the portage tree - @type search_key: string - @param masked: if True, also look for masked packages - @type masked: boolean - @param only_installed: if True, only installed packages are searched - @type only_installed: boolean - @param only_cpv: do not return package but only the cpv - @type only_cpv: boolean - - @returns: the package found or None - @rtype: backend.Package or string - """ - - raise NotImplementedError - - def find_packages (self, key, pkgSet = SET_ALL, masked = False, with_version = True, only_cpv = False): - """This returns a list of packages matching the key. - As key, it is allowed to use basic regexps (".*") and the normal package specs. But not a combination - of them. - - @param key: the key to look for - @type key: string - @param all: the package set to use - @type all: string - @param masked: if True, also look for masked packages - @type masked: boolean - @param with_version: if True, return CPVs - else CP - @type with_version: boolean - @param only_cpv: do not return package but only the cpv. if with_version is False, this is ignored - @type only_cpv: boolean - - @returns: list of found packages - @rtype: backend.Package[] or string[] - """ - - raise NotImplementedError - - def list_categories (self, name = None): - """Finds all categories matching a name or all if no name is specified. - - @param name: the name to look for - it is expanded to .*name.* ; if None, all categories are returned - @type name: string or None - @returns: all categories found - @rtype: string[] - """ - - raise NotImplementedError - - def sort_package_list(self, pkglist): - """Sorts a package list in the same manner portage does. - - @param pkglist: list to sort - @type pkglist: Packages[] - """ - - raise NotImplementedError - - def reload_settings (self): - """Reloads portage.""" - - raise NotImplementedError - - def update_world (self, newuse = False, deep = False): - """Calculates the packages to get updated in an update world. - - @param newuse: Checks if a use-flag has a different state then to install time. - @type newuse: boolean - @param deep: Not only check world packages but also there dependencies. - @type deep: boolean - @returns: a list of the tuple (new_package, old_package) - @rtype: (backend.Package, backend.Package)[] - """ - - raise NotImplementedError - - def get_updated_packages (self): - """Returns the packages for which a newer package is available in the portage tree and installable (thus not masked). - This differs from update_world as it takes all installed packages into account but ignores changed useflags. - - @returns: the list of new packages - @rtype: backend.Package[] - """ - - raise NotImplementedError - - def get_use_desc (self, flag, package = None): - """Returns the description of a specific useflag or None if no desc was found. - If a package is given (in the / format) the local use descriptions are searched too. - - @param flag: flag to get the description for - @type flag: string - @param package: name of a package: if given local use descriptions are searched too - @type package: cp-string - @returns: found description - @rtype: string - """ - - raise NotImplementedError - - def get_global_settings(self, key): - """Returns the value of a global setting, i.e. ARCH, USE, ROOT, DISTDIR etc. - - @param key: the setting to return - @type key: string - @returns: the value of this setting - @rtype: string - """ + + SET_ALL = "__portato_all__" + SET_TREE = "__portato_tree__" + SET_INSTALLED = "__portato_installed__" + SET_UNINSTALLED = "__portato_uninstalled__" + + def has_set_support (self): + """Signals, whether this backend supports sets. + + @rtype: boolean + """ + raise NotImplementedError + + def get_sets (self): + """Returns all supported sets in tuples consisting of name and description. + If sets aren't supported, at least "world" and "system" have to be returned. + + @rtype: iter(string, string) + """ + raise NotImplementedError + + def get_version (self): + """Returns the version of the used backend. + + @rtype: string + """ + raise NotImplementedError + + def split_cpv (self, cpv): + """Splits a cpv into all its parts. + + @param cpv: the cpv to split + @type cpv: string + @returns: the splitted cpv + @rtype: string[] + """ + + raise NotImplementedError + + def cpv_matches (self, cpv, criterion): + """Checks whether a cpv matches a specific criterion. + + @param cpv: cpv to check + @type cpv: string + @param criterion: criterion to check against + @type criterion: string + @returns: match result + @rtype: boolean + """ + + raise NotImplementedError + + def find_best(self, list, only_cpv = False): + """Returns the best package out of a list of packages. + + @param list: the list of packages to select from + @type list: string[] + @param only_cpv: do not return package but only the cpv + @type only_cpv: boolean + + @returns: the best package + @rtype: backend.Package or string + """ + + raise NotImplementedError + + def find_best_match (self, search_key, masked = False, only_installed = False, only_cpv = False): + """Finds the best match in the portage tree. It does not find masked packages! + + @param search_key: the key to find in the portage tree + @type search_key: string + @param masked: if True, also look for masked packages + @type masked: boolean + @param only_installed: if True, only installed packages are searched + @type only_installed: boolean + @param only_cpv: do not return package but only the cpv + @type only_cpv: boolean + + @returns: the package found or None + @rtype: backend.Package or string + """ + + raise NotImplementedError + + def find_packages (self, key, pkgSet = SET_ALL, masked = False, with_version = True, only_cpv = False): + """This returns a list of packages matching the key. + As key, it is allowed to use basic regexps (".*") and the normal package specs. But not a combination + of them. + + @param key: the key to look for + @type key: string + @param all: the package set to use + @type all: string + @param masked: if True, also look for masked packages + @type masked: boolean + @param with_version: if True, return CPVs - else CP + @type with_version: boolean + @param only_cpv: do not return package but only the cpv. if with_version is False, this is ignored + @type only_cpv: boolean + + @returns: list of found packages + @rtype: backend.Package[] or string[] + """ + + raise NotImplementedError + + def list_categories (self, name = None): + """Finds all categories matching a name or all if no name is specified. + + @param name: the name to look for - it is expanded to .*name.* ; if None, all categories are returned + @type name: string or None + @returns: all categories found + @rtype: string[] + """ + + raise NotImplementedError + + def sort_package_list(self, pkglist): + """Sorts a package list in the same manner portage does. + + @param pkglist: list to sort + @type pkglist: Packages[] + """ + + raise NotImplementedError + + def reload_settings (self): + """Reloads portage.""" + + raise NotImplementedError + + def update_world (self, newuse = False, deep = False): + """Calculates the packages to get updated in an update world. + + @param newuse: Checks if a use-flag has a different state then to install time. + @type newuse: boolean + @param deep: Not only check world packages but also there dependencies. + @type deep: boolean + @returns: a list of the tuple (new_package, old_package) + @rtype: (backend.Package, backend.Package)[] + """ + + raise NotImplementedError + + def get_updated_packages (self): + """Returns the packages for which a newer package is available in the portage tree and installable (thus not masked). + This differs from update_world as it takes all installed packages into account but ignores changed useflags. + + @returns: the list of new packages + @rtype: backend.Package[] + """ + + raise NotImplementedError + + def get_use_desc (self, flag, package = None): + """Returns the description of a specific useflag or None if no desc was found. + If a package is given (in the / format) the local use descriptions are searched too. + + @param flag: flag to get the description for + @type flag: string + @param package: name of a package: if given local use descriptions are searched too + @type package: cp-string + @returns: found description + @rtype: string + """ + + raise NotImplementedError + + def get_global_settings(self, key): + """Returns the value of a global setting, i.e. ARCH, USE, ROOT, DISTDIR etc. + + @param key: the setting to return + @type key: string + @returns: the value of this setting + @rtype: string + """ - raise NotImplementedError + raise NotImplementedError - def new_package (self, cpv): - """Returns an instance of the appropriate Package-Subclass. + def new_package (self, cpv): + """Returns an instance of the appropriate Package-Subclass. - @param cpv: the cpv to create the package from - @type cpv: string - @returns: a new Package-object. - @rtype: Package - """ + @param cpv: the cpv to create the package from + @type cpv: string + @returns: a new Package-object. + @rtype: Package + """ - raise NotImplementedError + raise NotImplementedError - def get_config_path (self): - """Returns the actual path to the config files. - - @returns: the path, e.g. /etc/portage - @rtype: string - """ + def get_config_path (self): + """Returns the actual path to the config files. + + @returns: the path, e.g. /etc/portage + @rtype: string + """ - raise NotImplementedError + raise NotImplementedError - def get_sync_command (self): - """Returns the command(s) to run for syncing. This can be overridden by the user. + def get_sync_command (self): + """Returns the command(s) to run for syncing. This can be overridden by the user. - @returns: command to run - @rtype: string[] - """ + @returns: command to run + @rtype: string[] + """ - raise NotImplementedError + raise NotImplementedError - def get_merge_command (self): - """Returns the command(s) to run for the merging. + def get_merge_command (self): + """Returns the command(s) to run for the merging. - @returns: command to run - @rtype: string[] - """ + @returns: command to run + @rtype: string[] + """ - raise NotImplementedError + raise NotImplementedError - def get_oneshot_option (self): - """Returns the options to append for marking a merge as "oneshot". + def get_oneshot_option (self): + """Returns the options to append for marking a merge as "oneshot". - @returns: option(s) to append - @rtype: string[] - """ + @returns: option(s) to append + @rtype: string[] + """ - raise NotImplementedError + raise NotImplementedError - def get_newuse_option (self): - """Returns the options to append for marking a merge as "newuse". + def get_newuse_option (self): + """Returns the options to append for marking a merge as "newuse". - @returns: option(s) to append - @rtype: string[] - """ + @returns: option(s) to append + @rtype: string[] + """ - raise NotImplementedError + raise NotImplementedError - def get_deep_option (self): - """Returns the options to append for marking a merge as "deep". + def get_deep_option (self): + """Returns the options to append for marking a merge as "deep". - @returns: option(s) to append - @rtype: string[] - """ + @returns: option(s) to append + @rtype: string[] + """ - raise NotImplementedError + raise NotImplementedError - def get_update_option (self): - """Returns the options to append for marking a merge as "update". + def get_update_option (self): + """Returns the options to append for marking a merge as "update". - @returns: option(s) to append - @rtype: string[] - """ + @returns: option(s) to append + @rtype: string[] + """ - raise NotImplementedError + raise NotImplementedError - def get_pretend_option (self): - """Returns the options to append for marking a merge as "pretend". + def get_pretend_option (self): + """Returns the options to append for marking a merge as "pretend". - @returns: option(s) to append - @rtype: string[] - """ + @returns: option(s) to append + @rtype: string[] + """ - raise NotImplementedError + raise NotImplementedError - def get_unmerge_option (self): - """Returns the options to append for marking a merge as "unmerge". + def get_unmerge_option (self): + """Returns the options to append for marking a merge as "unmerge". - @returns: option(s) to append - @rtype: string[] - """ + @returns: option(s) to append + @rtype: string[] + """ - raise NotImplementedError + raise NotImplementedError - def get_environment (self): - """Returns a dictionary of environment variables to set prior to do an emerge. + def get_environment (self): + """Returns a dictionary of environment variables to set prior to do an emerge. - @returns: environment variables - @rtype: dict{string : string} - """ + @returns: environment variables + @rtype: dict{string : string} + """ - raise NotImplementedError + raise NotImplementedError diff --git a/portato/config_parser.py b/portato/config_parser.py index 6515d1b..e3f78db 100644 --- a/portato/config_parser.py +++ b/portato/config_parser.py @@ -19,25 +19,25 @@ Thus it keeps comments and structuring of the file. :Variables: - DELIMITER : string[] - list of delimiters allowed + DELIMITER : string[] + list of delimiters allowed - COMMENT : string [] - comment marks allowed + COMMENT : string [] + comment marks allowed - TRUE - Regular expression for all TRUE values allowed. - Currently supported are the values (case insensitive): true, 1, on, wahr, ja, yes. + TRUE + Regular expression for all TRUE values allowed. + Currently supported are the values (case insensitive): true, 1, on, wahr, ja, yes. - FALSE - Regular expression for all FALSE values allowed. - Currently supported are the values (case insensitive): false, 0, off, falsch, nein, no. + FALSE + Regular expression for all FALSE values allowed. + Currently supported are the values (case insensitive): false, 0, off, falsch, nein, no. - SECTION - Regular expression allowing the recognition of a section header. + SECTION + Regular expression allowing the recognition of a section header. - EXPRESSION - Regular expression defining a normal option-value pair. + EXPRESSION + Regular expression defining a normal option-value pair. """ from __future__ import absolute_import, with_statement @@ -58,473 +58,473 @@ SECTION = re.compile("\s*\[(?P\w(\w|[-_])*)\]\s*") EXPRESSION = re.compile(r"\s*(?P\w(\w|[-_])*)\s*[:=]\s*(?P.*)\s*") class KeyNotFoundException (KeyError): - """ - Exception signaling, that a specific key could not be found in the configuration. - """ - pass + """ + Exception signaling, that a specific key could not be found in the configuration. + """ + pass class SectionNotFoundException (KeyError): - """ - Exception signaling, that a section could not be found in the configuration. - """ - pass + """ + Exception signaling, that a section could not be found in the configuration. + """ + pass class Value (object): - """ - Class defining a value of a key. - - :IVariables: - - value - The specific value. - - old - The old value - - line : int - The line in the config file. - - boolean : boolean - The boolean meaning of this value. Set this to ``None`` if this is not a boolean. - - changed : boolean - Set to True if the value has been changed. - """ - - - def __init__ (self, value, line, bool = None): - """ - Constructor. - - :Parameters: - - value : string - the value - - line : int - the line in the config file - - bool : boolean - The boolean meaning of the value. Set this to ``None`` if this is not a boolean. - """ - - self.__value = value - self.line = line - self.boolean = bool - - self.changed = False # true if we changed it - self.old = value # keep the original one ... so if we change it back to this one, we do not have to write - - def set (self, value): - """ - Sets the value to a new one. - - :param value: new value - :type value: string - """ - - self.__value = value - - if value != self.old: - self.changed = True - else: - self.changed = False - - def get (self): - """ - Returns the actual value. - - :rtype: string - """ - - return self.__value - - def is_bool (self): - """ - Returns whether the actual value has a boolean meaning. - - :rtype: boolean - """ - - return (self.boolean != None) - - def __str__ (self): - return str(self.__value) - - def __repr__ (self): - return self.__str__() - - value = property(get,set) - + """ + Class defining a value of a key. + + :IVariables: + + value + The specific value. + + old + The old value + + line : int + The line in the config file. + + boolean : boolean + The boolean meaning of this value. Set this to ``None`` if this is not a boolean. + + changed : boolean + Set to True if the value has been changed. + """ + + + def __init__ (self, value, line, bool = None): + """ + Constructor. + + :Parameters: + + value : string + the value + + line : int + the line in the config file + + bool : boolean + The boolean meaning of the value. Set this to ``None`` if this is not a boolean. + """ + + self.__value = value + self.line = line + self.boolean = bool + + self.changed = False # true if we changed it + self.old = value # keep the original one ... so if we change it back to this one, we do not have to write + + def set (self, value): + """ + Sets the value to a new one. + + :param value: new value + :type value: string + """ + + self.__value = value + + if value != self.old: + self.changed = True + else: + self.changed = False + + def get (self): + """ + Returns the actual value. + + :rtype: string + """ + + return self.__value + + def is_bool (self): + """ + Returns whether the actual value has a boolean meaning. + + :rtype: boolean + """ + + return (self.boolean != None) + + def __str__ (self): + return str(self.__value) + + def __repr__ (self): + return self.__str__() + + value = property(get,set) + class ConfigParser: - """ - The parser class. - - :CVariables: - - true_false : string -> string - A mapping from the truth values to their opposits. - - :IVariables: - - file : string - the file to scan - cache : string[] - caches the content of the file - vars : string -> (string -> `Value`) - the found options grouped by section - pos : int -> (int, int) - the positions of the values grouped by lineno - """ - - # generates the complementary true-false-pairs - true_false = { - "true" : "false", - "1" : "0", - "on" : "off", - "yes" : "no", - "ja" : "nein", - "wahr" : "falsch"} - true_false.update(zip(true_false.values(), true_false.keys())) - - def __init__ (self, file): - """ - Constructor. - - :param file: the configuration file to open - :type file: string - """ - - self.file = file - self.writelock = Lock() - self.__initialize() - - def __initialize (self): - """Private method which initializes our dictionaries.""" - - self.vars = {"MAIN": {}} - self.cache = [] # file cache - self.pos = {} # stores the positions of the matches - self.sections = {"MAIN" : -1} # the line with the section header - - def _invert (self, val): - """ - Invertes a given boolean. - - :param val: value to invert - :type val: string - :returns: inverted value - :rtype: string - - :see: `true_false` - """ - - return self.true_false[val.lower()] - - def parse (self): - """ - Parses the file. - """ - - # read into cache - with open(self.file, "r") as f: - self.cache = f.readlines() - - # implicit first section is main - section = "MAIN" - count = -1 - for line in self.cache: - count += 1 - - ls = line.strip() - if not ls: continue # empty - if ls[0] in COMMENT: continue # comment - - # look for a section - match = SECTION.search(line) - if match: - sec = match.group("name").upper() - self.sections[sec] = count - if sec != section: - self.vars[sec] = {} - section = sec - continue - - # look for an expression - match = EXPRESSION.search(line) - if match: - val = match.group("value") - - # find the boolean value - bool = None - if TRUE.match(val): - bool = True - elif FALSE.match(val): - bool = False - - # insert - key = match.group("key").lower() - self.vars[section][key] = Value(val, count, bool = bool) - self.pos[count] = match.span("value") - else: # neither comment nor empty nor expression nor section => error - error(_("Unrecognized line in configuration: %s"), line) - - def _access (self, key, section): - """ - Private method for accessing the saved variables. - - :Parameters: - - key : string - the key - section : string - the section - - :returns: the value wanted - :rtype: `Value` - - :Exceptions: - - - `KeyNotFoundException` : Raised if the specified key could not be found. - - `SectionNotFoundException` : Raised if the specified section could not be found. - """ - - try: - sectiondict = self.vars[section] - except KeyError: - raise SectionNotFoundException("Section '%s' not found in file '%s'." % (section, self.file)) - - try: - return sectiondict[key] - except KeyError: - raise KeyNotFoundException("Key '%s' not found in section '%s' in file '%s'." % (key, section, self.file)) - - def get (self, key, section = "MAIN"): - """ - Returns the value of a given key in a section. - - :Parameters: - - key : string - the key - section : string - the section - - :returns: value - :rtype: string - - :Exceptions: - - - `KeyNotFoundException` : Raised if the specified key could not be found. - - `SectionNotFoundException` : Raised if the specified section could not be found. - """ - - section = section.upper() - key = key.lower() - return self._access(key, section).value - - def get_boolean (self, key, section = "MAIN"): - """ - Returns the boolean value of a given key in a section. - - :Parameters: - - key : string - the key - section : string - the section - - :returns: value - :rtype: boolean - - :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. - """ - - section = section.upper() - key = key.lower() - - val = self._access(key, section) - - if val.is_bool(): - return val.boolean - - raise ValueError, "\"%s\" is not a boolean. (%s)" % (key, val.value) - - def set (self, key, value = "", section = "MAIN"): - """ - Sets a new value of a given key in a section. - - :Parameters: - - key : string - the key - value : string - the new value - section : string - the section - - :Exceptions: - - - `KeyNotFoundException` : Raised if the specified key could not be found. - - `SectionNotFoundException` : Raised if the specified section could not be found. - """ - - section = section.upper() - key = key.lower() - - self._access(key, section).value = value - - def set_boolean (self, key, value, section = "MAIN"): - """ - Sets a new boolean value of a given key in a section. - Therefore it invertes the string representation of the boolean (in lowercase). - - :Parameters: - - key : string - the key - value : boolean - the new value - section : string - the section - - :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 - """ - - section = section.upper() - key = key.lower() - - if not isinstance(value, bool): - raise ValueError, "Passed value must be a boolean." - - val = self._access(key, section) - if val.is_bool(): - if value is not val.boolean: - val.boolean = value - val.value = self._invert(val.value) - else: - raise ValueError, "\"%s\" is not a boolean." % key - - def add_section (self, section, comment = None, with_blankline = True): - """ - Adds a section to a the current configuration. If this section already exists, it does nothing. - - :Parameters: - - comment : string - An additional comment to place above this section. '\\n' in the comment is interpreted correctly. - - with_blankline : boolean - Add an additional blank line above the section. - """ - section = section.upper() - - if section in self.vars: - return - - if with_blankline and len(self.cache) > 0: - self.cache.append("\n") - - if comment: - if isinstance(comment, basestring): - comment = comment.split("\n") - - # add newlines to comment at the beginning and the end - comment.insert(0, "") - comment.append("") - - for c in comment: - self.cache.append("# %s\n" % c) - - self.vars[section] = {} - self.sections[section] = len(self.cache) - self.cache.append("[%s]\n" % section) - - def add (self, key, value, section = "MAIN", comment = None, with_blankline = True): - """ - Adds a key to the specified section. If the key already exists, it acts the same as `set`. - - :Parameters: - - key : string - The key to add. - section : string - The section where to add the key to. - comment : string - An additional comment for the key. '\\n' is correctly handled. - with_blankline : boolean - Add an additional blank line in front of the value. - - :raises SectionNotFoundException: if the section specified was not found - """ - - section = section.upper() - key = key.lower() - - try: - if key in self.vars[section]: - return self.set(key, value, section) - except KeyError: - raise SectionNotFoundException("Section '%s' not found in file '%s'." % (section, self.file)) - - self.write() - - # find line# to add - if self.vars[section]: - mline = max((x.line for x in self.vars[section].itervalues())) + 1 - else: # no value inside the section at the moment - mline = self.sections[section] + 1 - - if with_blankline and mline > 0: - self.cache.insert(mline, "\n") - mline += 1 - - if comment: - if isinstance(comment, basestring): - comment = comment.split("\n") - - for c in comment: - self.cache.insert(mline, "; %s\n" % c) - mline += 1 - - self.cache.insert(mline, "%s = %s\n" % (key, value)) - - self.write() - - def write (self): - """ - Writes the configuration file. - """ - - if not self.cache: - return - - with self.writelock: - for sec in self.vars: - for val in self.vars[sec].itervalues(): - if val.changed: - part1 = self.cache[val.line][:self.pos[val.line][0]] # key+DELIMITER - part2 = val.value # value - part3 = self.cache[val.line][self.pos[val.line][1]:] # everything behind the value (\n in normal cases) - - if not val.old and part1.endswith("\n"): # empty original value - part1 = part1[:-1] # strip \n - part3 = part3 + "\n" - - self.cache[val.line] = part1 + part2 + part3 - - # write - with open(self.file, "w") as f: - f.writelines(self.cache) - - # reload - self.__initialize() - self.parse() + """ + The parser class. + + :CVariables: + + true_false : string -> string + A mapping from the truth values to their opposits. + + :IVariables: + + file : string + the file to scan + cache : string[] + caches the content of the file + vars : string -> (string -> `Value`) + the found options grouped by section + pos : int -> (int, int) + the positions of the values grouped by lineno + """ + + # generates the complementary true-false-pairs + true_false = { + "true" : "false", + "1" : "0", + "on" : "off", + "yes" : "no", + "ja" : "nein", + "wahr" : "falsch"} + true_false.update(zip(true_false.values(), true_false.keys())) + + def __init__ (self, file): + """ + Constructor. + + :param file: the configuration file to open + :type file: string + """ + + self.file = file + self.writelock = Lock() + self.__initialize() + + def __initialize (self): + """Private method which initializes our dictionaries.""" + + self.vars = {"MAIN": {}} + self.cache = [] # file cache + self.pos = {} # stores the positions of the matches + self.sections = {"MAIN" : -1} # the line with the section header + + def _invert (self, val): + """ + Invertes a given boolean. + + :param val: value to invert + :type val: string + :returns: inverted value + :rtype: string + + :see: `true_false` + """ + + return self.true_false[val.lower()] + + def parse (self): + """ + Parses the file. + """ + + # read into cache + with open(self.file, "r") as f: + self.cache = f.readlines() + + # implicit first section is main + section = "MAIN" + count = -1 + for line in self.cache: + count += 1 + + ls = line.strip() + if not ls: continue # empty + if ls[0] in COMMENT: continue # comment + + # look for a section + match = SECTION.search(line) + if match: + sec = match.group("name").upper() + self.sections[sec] = count + if sec != section: + self.vars[sec] = {} + section = sec + continue + + # look for an expression + match = EXPRESSION.search(line) + if match: + val = match.group("value") + + # find the boolean value + bool = None + if TRUE.match(val): + bool = True + elif FALSE.match(val): + bool = False + + # insert + key = match.group("key").lower() + self.vars[section][key] = Value(val, count, bool = bool) + self.pos[count] = match.span("value") + else: # neither comment nor empty nor expression nor section => error + error(_("Unrecognized line in configuration: %s"), line) + + def _access (self, key, section): + """ + Private method for accessing the saved variables. + + :Parameters: + + key : string + the key + section : string + the section + + :returns: the value wanted + :rtype: `Value` + + :Exceptions: + + - `KeyNotFoundException` : Raised if the specified key could not be found. + - `SectionNotFoundException` : Raised if the specified section could not be found. + """ + + try: + sectiondict = self.vars[section] + except KeyError: + raise SectionNotFoundException("Section '%s' not found in file '%s'." % (section, self.file)) + + try: + return sectiondict[key] + except KeyError: + raise KeyNotFoundException("Key '%s' not found in section '%s' in file '%s'." % (key, section, self.file)) + + def get (self, key, section = "MAIN"): + """ + Returns the value of a given key in a section. + + :Parameters: + + key : string + the key + section : string + the section + + :returns: value + :rtype: string + + :Exceptions: + + - `KeyNotFoundException` : Raised if the specified key could not be found. + - `SectionNotFoundException` : Raised if the specified section could not be found. + """ + + section = section.upper() + key = key.lower() + return self._access(key, section).value + + def get_boolean (self, key, section = "MAIN"): + """ + Returns the boolean value of a given key in a section. + + :Parameters: + + key : string + the key + section : string + the section + + :returns: value + :rtype: boolean + + :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. + """ + + section = section.upper() + key = key.lower() + + val = self._access(key, section) + + if val.is_bool(): + return val.boolean + + raise ValueError, "\"%s\" is not a boolean. (%s)" % (key, val.value) + + def set (self, key, value = "", section = "MAIN"): + """ + Sets a new value of a given key in a section. + + :Parameters: + + key : string + the key + value : string + the new value + section : string + the section + + :Exceptions: + + - `KeyNotFoundException` : Raised if the specified key could not be found. + - `SectionNotFoundException` : Raised if the specified section could not be found. + """ + + section = section.upper() + key = key.lower() + + self._access(key, section).value = value + + def set_boolean (self, key, value, section = "MAIN"): + """ + Sets a new boolean value of a given key in a section. + Therefore it invertes the string representation of the boolean (in lowercase). + + :Parameters: + + key : string + the key + value : boolean + the new value + section : string + the section + + :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 + """ + + section = section.upper() + key = key.lower() + + if not isinstance(value, bool): + raise ValueError, "Passed value must be a boolean." + + val = self._access(key, section) + if val.is_bool(): + if value is not val.boolean: + val.boolean = value + val.value = self._invert(val.value) + else: + raise ValueError, "\"%s\" is not a boolean." % key + + def add_section (self, section, comment = None, with_blankline = True): + """ + Adds a section to a the current configuration. If this section already exists, it does nothing. + + :Parameters: + + comment : string + An additional comment to place above this section. '\\n' in the comment is interpreted correctly. + + with_blankline : boolean + Add an additional blank line above the section. + """ + section = section.upper() + + if section in self.vars: + return + + if with_blankline and len(self.cache) > 0: + self.cache.append("\n") + + if comment: + if isinstance(comment, basestring): + comment = comment.split("\n") + + # add newlines to comment at the beginning and the end + comment.insert(0, "") + comment.append("") + + for c in comment: + self.cache.append("# %s\n" % c) + + self.vars[section] = {} + self.sections[section] = len(self.cache) + self.cache.append("[%s]\n" % section) + + def add (self, key, value, section = "MAIN", comment = None, with_blankline = True): + """ + Adds a key to the specified section. If the key already exists, it acts the same as `set`. + + :Parameters: + + key : string + The key to add. + section : string + The section where to add the key to. + comment : string + An additional comment for the key. '\\n' is correctly handled. + with_blankline : boolean + Add an additional blank line in front of the value. + + :raises SectionNotFoundException: if the section specified was not found + """ + + section = section.upper() + key = key.lower() + + try: + if key in self.vars[section]: + return self.set(key, value, section) + except KeyError: + raise SectionNotFoundException("Section '%s' not found in file '%s'." % (section, self.file)) + + self.write() + + # find line# to add + if self.vars[section]: + mline = max((x.line for x in self.vars[section].itervalues())) + 1 + else: # no value inside the section at the moment + mline = self.sections[section] + 1 + + if with_blankline and mline > 0: + self.cache.insert(mline, "\n") + mline += 1 + + if comment: + if isinstance(comment, basestring): + comment = comment.split("\n") + + for c in comment: + self.cache.insert(mline, "; %s\n" % c) + mline += 1 + + self.cache.insert(mline, "%s = %s\n" % (key, value)) + + self.write() + + def write (self): + """ + Writes the configuration file. + """ + + if not self.cache: + return + + with self.writelock: + for sec in self.vars: + for val in self.vars[sec].itervalues(): + if val.changed: + part1 = self.cache[val.line][:self.pos[val.line][0]] # key+DELIMITER + part2 = val.value # value + part3 = self.cache[val.line][self.pos[val.line][1]:] # everything behind the value (\n in normal cases) + + if not val.old and part1.endswith("\n"): # empty original value + part1 = part1[:-1] # strip \n + part3 = part3 + "\n" + + self.cache[val.line] = part1 + part2 + part3 + + # write + with open(self.file, "w") as f: + f.writelines(self.cache) + + # reload + self.__initialize() + self.parse() diff --git a/portato/dependency.py b/portato/dependency.py index cce7360..ef06e41 100644 --- a/portato/dependency.py +++ b/portato/dependency.py @@ -23,176 +23,176 @@ from .backend import system class Dependency (object): - """ - A simple dependency as it also is noted inside ebuilds. + """ + A simple dependency as it also is noted inside ebuilds. - :IVariables: + :IVariables: - dep : string - The dependency string. It is immutable. + dep : string + The dependency string. It is immutable. - satisfied : boolean - Is this dependency satisfied? - """ + satisfied : boolean + Is this dependency satisfied? + """ - def __init__ (self, dep): - """ - Creates a dependency out of a dep string. + def __init__ (self, dep): + """ + Creates a dependency out of a dep string. - :param dep: dependency string - :type dep: string - """ - self._dep = dep + :param dep: dependency string + :type dep: string + """ + self._dep = dep - def is_satisfied (self): - """ - Checks if this dependency is satisfied. + def is_satisfied (self): + """ + Checks if this dependency is satisfied. - :rtype: boolean - """ - return system.find_best_match(self.dep, only_cpv = True, only_installed = True) is not None + :rtype: boolean + """ + return system.find_best_match(self.dep, only_cpv = True, only_installed = True) is not None - def __cmp__ (self, b): - return cmp(self.dep, b.dep) + def __cmp__ (self, b): + return cmp(self.dep, b.dep) - def __hash__ (self): - return hash(self.dep) + def __hash__ (self): + return hash(self.dep) - def __str__ (self): - return "" % self.dep + def __str__ (self): + return "" % self.dep - __repr__ = __str__ + __repr__ = __str__ - @property - def dep (self): - return self._dep + @property + def dep (self): + return self._dep - satisfied = property(is_satisfied) + satisfied = property(is_satisfied) class OrDependency (Dependency): - """ - Dependency representing an "or". - - :note: Order is important. ``|| ( a b )`` != ``|| ( b a )`` - - :IVariables: - - dep : tuple(`Dependency`,...) - The dependencies. The tuple and the dependencies are immutable. - """ - - def __init__ (self, deps): - """ - Creates an or-dependency out of a list (or tuple) of deps. - - :param deps: The or'ed dependencies. - :type deps: iter - """ - - _dep = [] - for dep in deps: - if not hasattr(dep, "__iter__"): - assert not dep.endswith("?") - _dep.append(Dependency(dep)) - else: - _dep.append(AllOfDependency(dep)) - - self._dep = tuple(_dep) - - def __str__ (self): - return "<|| %s>" % str(self.dep) - - __repr__ = __str__ + """ + Dependency representing an "or". + + :note: Order is important. ``|| ( a b )`` != ``|| ( b a )`` + + :IVariables: + + dep : tuple(`Dependency`,...) + The dependencies. The tuple and the dependencies are immutable. + """ + + def __init__ (self, deps): + """ + Creates an or-dependency out of a list (or tuple) of deps. + + :param deps: The or'ed dependencies. + :type deps: iter + """ + + _dep = [] + for dep in deps: + if not hasattr(dep, "__iter__"): + assert not dep.endswith("?") + _dep.append(Dependency(dep)) + else: + _dep.append(AllOfDependency(dep)) + + self._dep = tuple(_dep) + + def __str__ (self): + return "<|| %s>" % str(self.dep) + + __repr__ = __str__ class AllOfDependency (Dependency): - """ - Dependency representing a set of packages inside "or". - If the or is: ``|| (a ( b c ) )`` the `AllOfDependency` would be the ``( b c )``. + """ + Dependency representing a set of packages inside "or". + If the or is: ``|| (a ( b c ) )`` the `AllOfDependency` would be the ``( b c )``. - :IVariables: + :IVariables: - dep : tuple(`Dependency`,...) - The dependencies . The tuple and the deps are immutable. - """ + dep : tuple(`Dependency`,...) + The dependencies . The tuple and the deps are immutable. + """ - def __init__ (self, deps): - """ - Creates an or-dependency out of a list (or tuple) of deps. + def __init__ (self, deps): + """ + Creates an or-dependency out of a list (or tuple) of deps. - :param deps: The dependencies. - :type deps: iter - """ + :param deps: The dependencies. + :type deps: iter + """ - self._dep = tuple(Dependency(dep) for dep in deps) + self._dep = tuple(Dependency(dep) for dep in deps) - def __str__ (self): - return "" % str(self.dep) - - __repr__ = __str__ + def __str__ (self): + return "" % str(self.dep) + + __repr__ = __str__ class DependencyTree (object): - """ - The DependencyTree shows all dependencies for a package and shows which useflags want which dependencies. + """ + The DependencyTree shows all dependencies for a package and shows which useflags want which dependencies. - :IVariables: + :IVariables: - deps : set(`Dependency`) - The list of dependencies which are not dependent on a useflag. + deps : set(`Dependency`) + The list of dependencies which are not dependent on a useflag. - flags : string -> `DependencyTree` - Holds the additional dependency trees per useflag. - """ + flags : string -> `DependencyTree` + Holds the additional dependency trees per useflag. + """ - def __init__ (self): + def __init__ (self): - self.deps = set() - self.flags = {} + self.deps = set() + self.flags = {} - def add (self, dep, *moredeps): - """ - Adds one or more normal dependencies to the tree. + def add (self, dep, *moredeps): + """ + Adds one or more normal dependencies to the tree. - :Parameters: + :Parameters: - dep : string - A dependency string. + dep : string + A dependency string. - moredeps - More parameters are allowed :) - """ - self.deps.add(Dependency(dep)) + moredeps + More parameters are allowed :) + """ + self.deps.add(Dependency(dep)) - for dep in moredeps: - self.deps.add(Dependency(dep)) + for dep in moredeps: + self.deps.add(Dependency(dep)) - def add_or (self, orlist): - """ - Adds a list of dependencies, which are or'ed. + def add_or (self, orlist): + """ + Adds a list of dependencies, which are or'ed. - :param orlist: the dependency list - :type orlist: iter - """ - self.deps.add(OrDependency(orlist)) + :param orlist: the dependency list + :type orlist: iter + """ + self.deps.add(OrDependency(orlist)) - def add_flag (self, flag): - """ - Adds a new useflag to this tree. - For convenience the newly created sub-tree is returned. + def add_flag (self, flag): + """ + Adds a new useflag to this tree. + For convenience the newly created sub-tree is returned. - :param flag: the new flag - :rtype: `DependencyTree` - """ - if not flag in self.flags: - self.flags[flag] = DependencyTree() + :param flag: the new flag + :rtype: `DependencyTree` + """ + if not flag in self.flags: + self.flags[flag] = DependencyTree() - return self.get_flag_tree(flag) + return self.get_flag_tree(flag) - def get_flag_tree (self, flag): - """ - Returns the sub-tree of a specific tree. + def get_flag_tree (self, flag): + """ + Returns the sub-tree of a specific tree. - :raises KeyError: if the flag is not (yet) in this tree - :rtype: `DependencyTree` - """ - return self.flags[flag] + :raises KeyError: if the flag is not (yet) in this tree + :rtype: `DependencyTree` + """ + return self.flags[flag] diff --git a/portato/gui/__init__.py b/portato/gui/__init__.py index 0df890c..5bbe4c8 100644 --- a/portato/gui/__init__.py +++ b/portato/gui/__init__.py @@ -16,13 +16,13 @@ from .. import get_listener from .exception_handling import register_ex_handler def run (): - from .windows.splash import SplashScreen - s = SplashScreen(_("Loading Backend")) + from .windows.splash import SplashScreen + s = SplashScreen(_("Loading Backend")) - register_ex_handler() - s.show() - - from .windows.main import MainWindow - m = MainWindow(s) - s.hide() - m.main() + register_ex_handler() + s.show() + + from .windows.main import MainWindow + m = MainWindow(s) + s.hide() + m.main() diff --git a/portato/gui/dialogs.py b/portato/gui/dialogs.py index bf7acc7..f178d2b 100644 --- a/portato/gui/dialogs.py +++ b/portato/gui/dialogs.py @@ -14,95 +14,95 @@ import gtk from ..helper import error def mail_failure_dialog(e): - dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Mail could not be sent")) - dialog.format_secondary_text(_("The error was: %s") % e) - ret = dialog.run() - dialog.destroy() - return ret + dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Mail could not be sent")) + dialog.format_secondary_text(_("The error was: %s") % e) + ret = dialog.run() + dialog.destroy() + return ret def queue_not_empty_dialog(): - dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_NONE, _("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() - return ret + 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() + return ret def io_ex_dialog (io_ex): - string = io_ex.strerror - if io_ex.filename: - string = string+": "+io_ex.filename - - error(string) - dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, string) - ret = dialog.run() - dialog.destroy() - return ret + string = io_ex.strerror + if io_ex.filename: + string = string+": "+io_ex.filename + + error(string) + dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, string) + ret = dialog.run() + dialog.destroy() + 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.") % {"blocked":blocked, "blocks" : blocks}) - dialog.format_secondary_text(_("Please unmerge the blocking package.")) - ret = dialog.run() - dialog.destroy() - return ret + 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 def not_root_dialog (): - errorMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("You are not root.")) - ret = errorMB.run() - errorMB.destroy() - return ret + errorMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("You are not root.")) + ret = errorMB.run() + errorMB.destroy() + 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.") % cpv ) - dialog.format_secondary_text(_("Do you want to unmask it and its dependencies?")) - ret = dialog.run() - dialog.destroy() - return ret + 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 def nothing_found_dialog (): - dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("Package not found!")) - ret = dialog.run() - dialog.destroy() - return ret + dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("Package not found!")) + ret = dialog.run() + dialog.destroy() + return ret 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, _("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() - hintMB.destroy() + check = gtk.CheckButton(_("Do not show this dialog again.")) + 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() + hintMB.destroy() - return ret, check.get_active() + return ret, check.get_active() def remove_deps_dialog (): - infoMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("You cannot remove dependencies. :)")) - ret = infoMB.run() - infoMB.destroy() - return ret + infoMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("You cannot remove dependencies. :)")) + ret = infoMB.run() + infoMB.destroy() + return ret def remove_updates_dialog(): - askMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, _("This is the updates queue. You cannot remove single elements.\nDo you want to clear the whole queue instead?")) - ret = askMB.run() - askMB.destroy() - return ret + askMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, _("This is the updates queue. You cannot remove single elements.\nDo you want to clear the whole queue instead?")) + ret = askMB.run() + askMB.destroy() + return ret def remove_queue_dialog (): - askMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, _("Do you really want to clear the whole queue?")) - ret = askMB.run() - askMB.destroy() - return ret + askMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, _("Do you really want to clear the whole queue?")) + ret = askMB.run() + askMB.destroy() + return ret def file_chooser_dialog (title, parent): - fc = gtk.FileChooserDialog(title = title, parent = parent, action = gtk.FILE_CHOOSER_ACTION_SAVE, buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) - fc.set_do_overwrite_confirmation(True) - ret = fc.run() + fc = gtk.FileChooserDialog(title = title, parent = parent, action = gtk.FILE_CHOOSER_ACTION_SAVE, buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) + fc.set_do_overwrite_confirmation(True) + ret = fc.run() - if ret == gtk.RESPONSE_ACCEPT: - ret = fc.get_filename() - else: - ret = None + if ret == gtk.RESPONSE_ACCEPT: + ret = fc.get_filename() + else: + ret = None - fc.destroy() - return ret + fc.destroy() + return ret diff --git a/portato/gui/exception_handling.py b/portato/gui/exception_handling.py index dae95ed..df555de 100644 --- a/portato/gui/exception_handling.py +++ b/portato/gui/exception_handling.py @@ -24,108 +24,108 @@ from .windows.mailinfo import MailInfoWindow from .utils import GtkThread class UncaughtExceptionDialog(gtk.MessageDialog): - """Original idea by Gustavo Carneiro - original code: http://www.daa.com.au/pipermail/pygtk/attachments/20030828/2d304204/gtkexcepthook.py.""" - - def __init__(self, type, value, tb, thread = None): - - super(UncaughtExceptionDialog,self).__init__(parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_NONE, message_format=_("A programming error has been detected during the execution of this program.")) - self.set_title(_("Bug Detected")) - self.format_secondary_text(_("It probably isn't fatal, but should be reported to the developers nonetheless.")) - - self.add_button(_("Show Details"), 1) - self.add_button(_("Send..."), 3) - self.add_button(gtk.STOCK_SAVE_AS, 2) - self.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) - - # Details - self.textview = gtk.TextView() - self.textview.set_editable(False) - self.textview.modify_font(pango.FontDescription("Monospace")) - - self.sw = gtk.ScrolledWindow(); - self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - self.sw.add(self.textview) - - self.tbFrame = gtk.Frame() - self.tbFrame.set_shadow_type(gtk.SHADOW_IN) - self.tbFrame.add(self.sw) - self.tbFrame.set_border_width(6) - - self.vbox.add(self.tbFrame) - - textbuffer = self.textview.get_buffer() - self.text = get_trace(type, value, tb) - if thread: - self.text = _("Exception in thread \"%(thread)s\":\n%(trace)s") % {"thread": thread, "trace": self.text} - textbuffer.set_text(self.text) - self.textview.set_size_request(gtk.gdk.screen_width()/2, gtk.gdk.screen_height()/3) - - self.details = self.tbFrame - self.set_position(gtk.WIN_POS_CENTER) - self.set_gravity(gtk.gdk.GRAVITY_CENTER) - - def run (self): - while True: - resp = super(UncaughtExceptionDialog, self).run() - if resp == 1: - self.details.show_all() - self.set_response_sensitive(1, False) - elif resp == 2: - debug("Want to save") - file = file_chooser_dialog(_("Save traceback..."), self) - if file: - debug("Save to %s", file) - - try: - with open(file, "w") as f: - f.writelines(self.text) - except IOError, e: - io_ex_dialog(e) - - else: - debug("Nothing to save") - elif resp == 3: - debug("Send bug per mail") - self.destroy() - MailInfoWindow(None, self.text) - return - else: - break - self.destroy() + """Original idea by Gustavo Carneiro - original code: http://www.daa.com.au/pipermail/pygtk/attachments/20030828/2d304204/gtkexcepthook.py.""" + + def __init__(self, type, value, tb, thread = None): + + super(UncaughtExceptionDialog,self).__init__(parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_NONE, message_format=_("A programming error has been detected during the execution of this program.")) + self.set_title(_("Bug Detected")) + self.format_secondary_text(_("It probably isn't fatal, but should be reported to the developers nonetheless.")) + + self.add_button(_("Show Details"), 1) + self.add_button(_("Send..."), 3) + self.add_button(gtk.STOCK_SAVE_AS, 2) + self.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) + + # Details + self.textview = gtk.TextView() + self.textview.set_editable(False) + self.textview.modify_font(pango.FontDescription("Monospace")) + + self.sw = gtk.ScrolledWindow(); + self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.sw.add(self.textview) + + self.tbFrame = gtk.Frame() + self.tbFrame.set_shadow_type(gtk.SHADOW_IN) + self.tbFrame.add(self.sw) + self.tbFrame.set_border_width(6) + + self.vbox.add(self.tbFrame) + + textbuffer = self.textview.get_buffer() + self.text = get_trace(type, value, tb) + if thread: + self.text = _("Exception in thread \"%(thread)s\":\n%(trace)s") % {"thread": thread, "trace": self.text} + textbuffer.set_text(self.text) + self.textview.set_size_request(gtk.gdk.screen_width()/2, gtk.gdk.screen_height()/3) + + self.details = self.tbFrame + self.set_position(gtk.WIN_POS_CENTER) + self.set_gravity(gtk.gdk.GRAVITY_CENTER) + + def run (self): + while True: + resp = super(UncaughtExceptionDialog, self).run() + if resp == 1: + self.details.show_all() + self.set_response_sensitive(1, False) + elif resp == 2: + debug("Want to save") + file = file_chooser_dialog(_("Save traceback..."), self) + if file: + debug("Save to %s", file) + + try: + with open(file, "w") as f: + f.writelines(self.text) + except IOError, e: + io_ex_dialog(e) + + else: + debug("Nothing to save") + elif resp == 3: + debug("Send bug per mail") + self.destroy() + MailInfoWindow(None, self.text) + return + else: + break + self.destroy() def convert (version): - """Converts a version given as int-tuple to a normal version string.""" - return ".".join(map(str, version)) + """Converts a version given as int-tuple to a normal version string.""" + return ".".join(map(str, version)) def get_version_infos(): - from ..constants import VERSION - from ..backend import system - - 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)))) + from ..constants import VERSION + from ..backend import system + + 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)))) def get_trace(type, value, tb): - trace = StringIO() - traceback.print_exception(type, value, tb, None, trace) - traceStr = trace.getvalue() - trace.close() - return traceStr + "\n" + get_version_infos() - + trace = StringIO() + traceback.print_exception(type, value, tb, None, trace) + traceStr = trace.getvalue() + trace.close() + return traceStr + "\n" + get_version_infos() + def register_ex_handler(): - - def handler(type, val, tb, thread = None): - def run_dialog(): - UncaughtExceptionDialog(type, val, tb, thread).run() - - if thread: - error(_("Exception in thread \"%(thread)s\":\n%(trace)s"), {"thread": thread, "trace": get_trace(type, val, tb)}) - else: - error(_("Exception:\n%s"), get_trace(type, val, tb)) - - gobject.idle_add(run_dialog) - - sys.excepthook = handler + + def handler(type, val, tb, thread = None): + def run_dialog(): + UncaughtExceptionDialog(type, val, tb, thread).run() + + if thread: + error(_("Exception in thread \"%(thread)s\":\n%(trace)s"), {"thread": thread, "trace": get_trace(type, val, tb)}) + else: + error(_("Exception:\n%s"), get_trace(type, val, tb)) + + gobject.idle_add(run_dialog) + + sys.excepthook = handler diff --git a/portato/gui/queue.py b/portato/gui/queue.py index b5fb736..5ff600f 100644 --- a/portato/gui/queue.py +++ b/portato/gui/queue.py @@ -31,620 +31,620 @@ from .updater import Updater from .wrapper import GtkConsole, GtkTree class EmergeQueue: - """This class manages the emerge queue.""" - - def __init__ (self, tree = None, console = None, db = None, title_update = None, threadClass = threading.Thread): - """Constructor. - - @param tree: Tree to append all the items to. - @type tree: GtkTree - @param console: Output is shown here. - @type console: GtkConsole - @param db: A database instance. - @type db: Database - @param title_update: A function, which will be called whenever there is a title update. - @type title_update: function(string)""" - - # the different queues - self.mergequeue = [] # for emerge - self.unmergequeue = [] # for emerge -C - self.oneshotmerge = [] # for emerge --oneshot - - # the emerge process - self.process = None - self.threadQueue = WaitingQueue(threadClass = threadClass) - self.pty = None - - # dictionaries with data about the packages in the queue - self.iters = {"install" : {}, "uninstall" : {}, "update" : {}} # iterator in the tree - self.deps = {"install" : {}, "update" : {}} # all the deps of the package - self.blocks = {"install" : OrderedDict(), "update" : OrderedDict()} - - # member vars - self.tree = tree - if self.tree and not isinstance(self.tree, GtkTree): raise TypeError, "tree passed is not a GtkTree-object" - - self.console = console - if self.console and not isinstance(self.console, GtkConsole): raise TypeError, "console passed is not a GtkConsole-object" - - self.db = db - self.title_update = title_update - self.threadClass = threadClass - - if self.console: - self.pty = pty.openpty() - self.console.set_pty(self.pty[0]) - - def _get_pkg_from_cpv (self, cpv, unmask = False): - """Gets a L{backend.Package}-object from a cpv. - - @param cpv: the cpv to get the package for - @type cpv: string (cpv) - @param unmask: if True we will look for masked packages if we cannot find unmasked ones - @type unmask: boolean - @return: created package - @rtype: backend.Package - - @raises backend.PackageNotFoundException: If no package could be found - normally it is existing but masked.""" - - # for the beginning: let us create a package object - but it is not guaranteed, that it actually exists in portage - pkg = system.new_package(cpv) - masked = not (pkg.is_masked() or pkg.is_testing(use_keywords=True)) # we are setting this to True in case we have unmasked it already, but portage does not know this - - # and now try to find it in portage - pkg = system.find_packages("="+cpv, masked = masked) - - if pkg: # gotcha - pkg = pkg[0] - - elif unmask: # no pkg returned, but we are allowed to unmask it - pkg = system.find_packages("="+cpv, masked = True) - - if not pkg: - raise backend.PackageNotFoundException(cpv) # also not found - else: - pkg = pkg[0] - - if pkg.is_testing(use_keywords = True): - pkg.set_testing(True) - if pkg.is_masked(): - pkg.set_masked() - - else: # no pkg returned - and we are not allowed to unmask - raise backend.PackageNotFoundException(cpv) - - return pkg - - def update_tree (self, it, cpv, unmask = False, oneshot = False, type = "install"): - """This updates the tree recursivly, or? Isn't it? Bjorn! - - @param it: iterator where to append - @type it: Iterator - @param cpv: The package to append. - @type cpv: string (cat/pkg-ver) - @param unmask: True if we are allowed to look for masked packages - @type unmask: boolean - @param oneshot: True if we want to emerge is oneshot - @type oneshot: boolean - @param type: the type of the updating - @type type: string - - @raises backend.BlockedException: When occured during dependency-calculation. - @raises backend.PackageNotFoundException: If no package could be found - normally it is existing but masked.""" - - if cpv in self.deps[type]: - return # in list already and therefore it's already in the tree too - - # try to find an already installed instance - update = False - downgrade = False - uVersion = None - changedUse = [] - try: - pkg = self._get_pkg_from_cpv(cpv, unmask) - if not pkg.is_installed(): - old = system.find_packages(pkg.get_slot_cp(), system.SET_INSTALLED) - if old: - old = old[0] # assume we have only one there - cmp = pkg.compare_version(old) - if cmp > 0: - update = True - elif cmp < 0: - downgrade = True - - uVersion = old.get_version() - - old_iuse = set(old.get_iuse_flags()) - new_iuse = set(pkg.get_iuse_flags()) - - for i in old_iuse.difference(new_iuse): - changedUse.append("-"+i) - - for i in new_iuse.difference(old_iuse): - changedUse.append("+"+i) - else: - old_iuse = set(pkg.get_iuse_flags(installed = True)) - new_iuse = set(pkg.get_iuse_flags(installed = False)) - - for i in old_iuse.difference(new_iuse): - changedUse.append("-"+i) - - for i in new_iuse.difference(old_iuse): - changedUse.append("+"+i) - - except backend.PackageNotFoundException, e: # package not found / package is masked -> delete current tree and re-raise the exception - if type == "update": # remove complete tree - self.remove_with_children(self.tree.first_iter(it), removeNewFlags = False) - - elif type == "install": # remove only the intentionally added package - top = self.tree.first_iter(it) - parent = self.tree.parent_iter(it) - - if parent: - while not self.tree.iter_equal(top, parent): - parent = self.tree.parent_iter(parent) - it = self.tree.parent_iter(it) - - self.remove_with_children(it, removeNewFlags = False) - - if not self.tree.iter_has_children(top): # remove completely if nothing left - self.remove(top) - raise - - # add iter - subIt = self.tree.append(it, self.tree.build_append_value(cpv, oneshot = oneshot, update = update, downgrade = downgrade, version = uVersion, useChange = changedUse)) - self.iters[type][cpv] = subIt - - # get dependencies - deps = pkg.get_dep_packages(return_blocks = True) - self.deps[type][cpv] = deps - - for d in deps: - if d[0] == "!": # block - dep = d[1:] - if not dep in self.blocks[type]: - self.blocks[type][dep] = set() - - self.blocks[type][dep].add(cpv) - else: # recursive call - self.update_tree(subIt, d, unmask, type = type) - - def append (self, cpv, type = "install", update = False, forceUpdate = False, unmask = False, oneshot = False): - """Appends a cpv either to the merge queue or to the unmerge-queue. - Also updates the tree-view. - - @param cpv: Package to add - @type cpv: string (cat/pkg-ver) - @param type: The type of this append process. Possible values are "install", "uninstall", "update". - @type type: string - @param update: Set to True if a package is going to be updated (e.g. if the use-flags changed). - @type update: boolean - @param forceUpdate: Set to True if the update should be forced. - @type forceUpdate: boolean - @param unmask: True if we are allowed to look for masked packages - @type unmask: boolean - @param oneshot: True if this package should not be added to the world-file. - @type oneshot: boolean - - @raises portato.backend.PackageNotFoundException: if trying to add a package which does not exist""" - - if type in ("install", "update"): # emerge - if update: - pkg = self._get_pkg_from_cpv(cpv, unmask) - deps = pkg.get_dep_packages(return_blocks = True) - - if not forceUpdate and cpv in self.deps[type] and deps == self.deps[type][cpv]: - return # nothing changed - return - else: - hasBeenInQueue = (cpv in self.mergequeue or cpv in self.oneshotmerge) - parentIt = self.tree.parent_iter(self.iters[type][cpv]) - - # delete it out of the tree - but NOT the changed flags - self.remove_with_children(self.iters[type][cpv], removeNewFlags = False) - - if hasBeenInQueue: # package has been in queue before - self._queue_append(cpv, oneshot) - - self.update_tree(parentIt, cpv, unmask, oneshot = oneshot, type = type) - else: # not update - if type == "install": - 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) - - # handle blocks - if self.blocks[type]: - # check whether anything blocks something in the queue - for block in self.blocks[type]: - for c in self.iters[type]: - if system.cpv_matches(c, block): - blocked = ", ".join(self.blocks[type][block]) - warning("'%s' is blocked by: %s", c, blocked) - self.remove_with_children(self.iters[type][c], False) - raise BlockedException(c, blocked) - - # - # check whether we block a version that we are going to replace nevertheless - # - - # get the blocks that block an installed package - inst = [] - for block in self.blocks[type]: - pkgs = system.find_packages(block, system.SET_INSTALLED) - if pkgs: - inst.append((pkgs, block)) - - # the slot-cp's of the packages in the queue - slots = {} - for c in self.iters[type]: - slots[system.new_package(c).get_slot_cp()] = cpv - - # check the installed blocks against the slot-cp's - for pkgs, block in inst[:]: - done = False - for pkg in pkgs: - done = False - if pkg.get_slot_cp() in slots: - debug("Block '%s' can be ignored, because the blocking package is going to be replaced with '%s'.", block, slots[pkg.get_slot_cp()]) - done = True - if done: - inst.remove((pkgs,block)) - - if inst: # there is still something left to block - for pkgs, block in inst: - blocked = ", ".join(self.blocks[type][block]) - warning("'%s' blocks the installation of: %s", pkgs[0].get_cpv(), blocked) - self.remove_with_children(self.iters[type][cpv], False) - raise BlockedException(blocked, pkgs[0].get_cpv()) - - else: # unmerge - self.unmergequeue.append(cpv) - if self.tree: # update tree - self.iters["uninstall"].update({cpv: self.tree.append(self.tree.get_unmerge_it(), self.tree.build_append_value(cpv))}) - - def _queue_append (self, cpv, oneshot = False): - """Convenience function appending a cpv either to self.mergequeue or to self.oneshotmerge. - - @param cpv: cpv to add - @type cpv: string (cpv) - @param oneshot: True if this package should not be added to the world-file. - @type oneshot: boolean""" - - if not oneshot: - if cpv not in self.mergequeue: - self.mergequeue.append(cpv) - else: - if cpv not in self.oneshotmerge: - self.oneshotmerge.append(cpv) - - def doEmerge (self, options, packages, it, *args, **kwargs): - top = None - if self.tree and it: - for v in it.itervalues(): - self.tree.set_in_progress(v) - top = self.tree.first_iter(v) - break - - self.threadQueue.put(self.__emerge, options, packages, it, top, *args, **kwargs) - - def __emerge (self, options, packages, it, top, command = None): - """Calls emerge and updates the terminal. - - @param options: options to send to emerge - @type options: string[] - @param packages: packages to emerge - @type packages: string[] - @param it: Iterators which point to these entries whose children will be removed after completion. - @type it: dict(string -> Iterator) - @param top: The top iterator - @type top: Iterator - @param command: the command to execute - default is "/usr/bin/python /usr/bin/emerge" - @type command: string[]""" - - @plugin.hook("emerge", packages = packages, command = command, console = self.console, title_update = self.title_update) - def sub_emerge(command): - if command is None: - command = system.get_merge_command() - - # open tty - if self.console: - self.console.reset() - - def pre (): - os.setsid() # new session - if self.console: - import fcntl, termios - fcntl.ioctl(self.pty[1], termios.TIOCSCTTY, 0) # set pty-slave as session tty - os.dup2(self.pty[1], 0) - os.dup2(self.pty[1], 1) - os.dup2(self.pty[1], 2) - - # get all categories that are being touched during the emerge process - cats = set(map(lambda x: x.split("/")[0], it.iterkeys())) - - # start emerge - self.process = Popen(command+options+packages, shell = False, env = system.get_environment(), preexec_fn = pre) - - # remove packages from queue - if self.tree and it and not self.tree.is_in_unmerge(top): - self.up = Updater(self, it, self.threadClass) - else: - self.up = None - - # update title - if self.console: - old_title = self.console.get_window_title() - while self.process and self.process.poll() is None: - if self.title_update : - title = self.console.get_window_title() - if title != old_title: - self.title_update(title) - old_title = title - time.sleep(0.5) - - if self.up: - self.up.stop() - if it: - self.tree.set_in_progress(top, False) - else: - self.remove(top) - elif self.tree and it: - self.remove_with_children(top) - - if self.title_update: self.title_update(None) - - if self.process is None: # someone resetted this - self.threadQueue.next() - return - else: - ret = self.process.returncode - self.process = None - self.threadQueue.next() - - @plugin.hook("after_emerge", packages = packages, retcode = ret) - def update_packages(): - if self.db: - for cat in cats: - self.db.reload(cat) - debug("Category %s refreshed", cat) - - update_packages() - - sub_emerge(command) - - def emerge (self, force = False, options = None): - """Emerges everything in the merge-queue. - - @param force: If False, '-pv' is send to emerge. Default: False. - @type force: boolean - @param options: Additional options to send to the emerge command - @type options: string[]""" - - def prepare(queue): - """Prepares the list of iterators and the list of packages.""" - list = [] - its = {} - for k in queue: - list += ["="+k] - if self.tree: - its.update({k : self.iters["install"][k]}) - - return list, its - - if self.tree: - ownit = self.iters["install"] - else: - ownit = {} - - # oneshot-queue - if self.oneshotmerge: - # prepare package-list for oneshot - list, its = prepare(self.oneshotmerge) - if not self.mergequeue :# the other one does not exist - remove completely - its = ownit - - s = system.get_oneshot_option() - if not force: s += system.get_pretend_option() - if options is not None: s += options - - self.doEmerge(s, list, its, caller = self.emerge) - - # normal queue - if self.mergequeue: - # prepare package-list - list, its = prepare(self.mergequeue) - if not self.oneshotmerge: # the other one does not exist - remove completely - its = ownit - - s = [] - if not force: s = system.get_pretend_option() - if options is not None: s += options - - self.doEmerge(s, list, its, caller = self.emerge) - - def unmerge (self, force = False, options = None): - """Unmerges everything in the umerge-queue. - - @param force: If False, '-pv' is send to emerge. Default: False. - @type force: boolean - @param options: Additional options to send to the emerge command - @type options: string[]""" - - if len(self.unmergequeue) == 0: return # nothing in queue - - list = self.unmergequeue[:] # copy the unmerge-queue - - # set options - s = system.get_unmerge_option() - if not force: s += system.get_pretend_option() - if options is not None: s += options - - if self.tree: - it = self.iters["uninstall"] - else: - it = {} - - self.doEmerge(s,list, it, caller = self.unmerge) - - def update_world(self, sets = ("world",), force = False, newuse = False, deep = False, options = None): - """Does an update world. newuse and deep are the arguments handed to emerge. - - @param sets: The sets to update. - @type sets: string[] - @param force: If False, '-pv' is send to emerge. Default: False. - @type force: boolean - @param newuse: If True, append newuse options - @type newuse: boolean - @param deep: If True, append deep options - @type deep: boolean - @param options: Additional options to send to the emerge command - @type options: string[]""" - - opts = system.get_update_option() - - if newuse: opts += system.get_newuse_option() - if deep: opts += system.get_deep_option() - if not force: opts += system.get_pretend_option() - if options is not None: opts += options - - if self.tree: - it = self.iters["update"] - else: - it = {} - - self.doEmerge(opts, list(sets), it, caller = self.update_world) - - def sync (self, command = None): - """Calls "emerge --sync". - - @param command: command to execute to sync. If None "emerge --sync" is taken. - @type command: string[]""" - - if command is None: - command = system.get_sync_command() - - try: - while True: - idx = command.index("&&") - self.doEmerge([],[],{}, command[:idx], caller = self.sync) - command = command[idx+1:] - except ValueError: # no && in command - self.doEmerge([],[],{}, command, caller = self.sync) - - def kill_emerge (self): - """Kills the emerge process.""" - if self.process is not None: - self.threadQueue.clear() # remove all pending emerge threads - try: - pgid = os.getpgid(self.process.pid) - os.killpg(pgid, signal.SIGTERM) - debug("Process should be terminated") - if self.process.poll() is None: - os.killpg(pgid, signal.SIGKILL) - debug("Process should be killed") - except AttributeError: - debug("AttributeError occured ==> process not exisiting - ignore") - except OSError: - debug("OSError occured ==> process already stopped - ignore") - - self.process = None - - def stop_emerge (self): - if self.process is not None: - os.killpg(os.getpgid(self.process.pid), signal.SIGSTOP) - debug("Process should be stopped") - - def continue_emerge (self): - if self.process is not None: - os.killpg(os.getpgid(self.process.pid), signal.SIGCONT) - debug("Process should continue") - - def remove_with_children (self, it, removeNewFlags = True): - """Convenience function which removes all children of an iterator and than the iterator itself. - - @param it: The iter which to remove. - @type it: Iterator - @param removeNewFlags: True if new flags should be removed; False otherwise. Default: True. - @type removeNewFlags: boolean""" - - self.remove_children(it, removeNewFlags) - self.remove(it, removeNewFlags) - - def remove_children (self, parentIt, removeNewFlags = True): - """Removes all children of a given parent TreeIter recursivly. - - @param parentIt: The iter from which to remove all children. - @type parentIt: Iterator - @param removeNewFlags: True if new flags should be removed; False otherwise. Default: True. - @type removeNewFlags: boolean""" - - childIt = self.tree.first_child_iter(parentIt) - - while childIt: - if (self.tree.iter_has_children(childIt)): # recursive call - self.remove_children(childIt, removeNewFlags) - temp = childIt - childIt = self.tree.next_iter(childIt) - self.remove(temp, removeNewFlags) - - def remove (self, it, removeNewFlags = True): - """Removes a specific item in the tree. This does not remove the top-entries. - - @param it: Iterator which points to the entry we are going to remove. - @type it: Iterator - @param removeNewFlags: True if new flags should be removed; False otherwise. Default: True. - @type removeNewFlags: boolean""" - - def __remove (type, cpv): - del self.iters[type][cpv] - try: - del self.deps[type][cpv] - except KeyError: # this seems to be removed due to a BlockedException - so no deps here atm ;) - debug("Catched KeyError => %s seems not to be in self.deps. Should be no harm in normal cases.", cpv) - - for key in self.blocks[type].keys(): - if cpv in self.blocks[type][key]: - self.blocks[type][key].remove(cpv) - - if not self.blocks[type][key]: # list is empty -> remove the whole key - del self.blocks[type][key] - - if removeNewFlags: # remove the changed flags - flags.remove_new_use_flags(cpv) - flags.remove_new_masked(cpv) - flags.remove_new_testing(cpv) - - if self.tree.iter_has_parent(it): - cpv = self.tree.get_value(it, self.tree.get_cpv_column()) - if self.tree.is_in_emerge(it): # Emerge - - __remove("install", cpv) - - try: - self.mergequeue.remove(cpv) - except ValueError: # this is a dependency - ignore - try: - self.oneshotmerge.remove(cpv) - except ValueError: - debug("Catched ValueError => %s seems not to be in merge-queue. Should be no harm.", cpv) - - elif self.tree.is_in_unmerge(it): # in Unmerge - del self.iters["uninstall"][cpv] - self.unmergequeue.remove(cpv) - - elif self.tree.is_in_update(it): - __remove("update", cpv) - - - self.tree.remove(it) - - def is_empty (self): - """Checks whether the current queue is empty and not working. Therefore it looks, whether the queues are empty, - and the process is not running. - - @returns: True if everything is empty and the process is not running. - @rtype: bool""" - - return not (self.process or any(map(len, self.iters.itervalues()))) + """This class manages the emerge queue.""" + + def __init__ (self, tree = None, console = None, db = None, title_update = None, threadClass = threading.Thread): + """Constructor. + + @param tree: Tree to append all the items to. + @type tree: GtkTree + @param console: Output is shown here. + @type console: GtkConsole + @param db: A database instance. + @type db: Database + @param title_update: A function, which will be called whenever there is a title update. + @type title_update: function(string)""" + + # the different queues + self.mergequeue = [] # for emerge + self.unmergequeue = [] # for emerge -C + self.oneshotmerge = [] # for emerge --oneshot + + # the emerge process + self.process = None + self.threadQueue = WaitingQueue(threadClass = threadClass) + self.pty = None + + # dictionaries with data about the packages in the queue + self.iters = {"install" : {}, "uninstall" : {}, "update" : {}} # iterator in the tree + self.deps = {"install" : {}, "update" : {}} # all the deps of the package + self.blocks = {"install" : OrderedDict(), "update" : OrderedDict()} + + # member vars + self.tree = tree + if self.tree and not isinstance(self.tree, GtkTree): raise TypeError, "tree passed is not a GtkTree-object" + + self.console = console + if self.console and not isinstance(self.console, GtkConsole): raise TypeError, "console passed is not a GtkConsole-object" + + self.db = db + self.title_update = title_update + self.threadClass = threadClass + + if self.console: + self.pty = pty.openpty() + self.console.set_pty(self.pty[0]) + + def _get_pkg_from_cpv (self, cpv, unmask = False): + """Gets a L{backend.Package}-object from a cpv. + + @param cpv: the cpv to get the package for + @type cpv: string (cpv) + @param unmask: if True we will look for masked packages if we cannot find unmasked ones + @type unmask: boolean + @return: created package + @rtype: backend.Package + + @raises backend.PackageNotFoundException: If no package could be found - normally it is existing but masked.""" + + # for the beginning: let us create a package object - but it is not guaranteed, that it actually exists in portage + pkg = system.new_package(cpv) + masked = not (pkg.is_masked() or pkg.is_testing(use_keywords=True)) # we are setting this to True in case we have unmasked it already, but portage does not know this + + # and now try to find it in portage + pkg = system.find_packages("="+cpv, masked = masked) + + if pkg: # gotcha + pkg = pkg[0] + + elif unmask: # no pkg returned, but we are allowed to unmask it + pkg = system.find_packages("="+cpv, masked = True) + + if not pkg: + raise backend.PackageNotFoundException(cpv) # also not found + else: + pkg = pkg[0] + + if pkg.is_testing(use_keywords = True): + pkg.set_testing(True) + if pkg.is_masked(): + pkg.set_masked() + + else: # no pkg returned - and we are not allowed to unmask + raise backend.PackageNotFoundException(cpv) + + return pkg + + def update_tree (self, it, cpv, unmask = False, oneshot = False, type = "install"): + """This updates the tree recursivly, or? Isn't it? Bjorn! + + @param it: iterator where to append + @type it: Iterator + @param cpv: The package to append. + @type cpv: string (cat/pkg-ver) + @param unmask: True if we are allowed to look for masked packages + @type unmask: boolean + @param oneshot: True if we want to emerge is oneshot + @type oneshot: boolean + @param type: the type of the updating + @type type: string + + @raises backend.BlockedException: When occured during dependency-calculation. + @raises backend.PackageNotFoundException: If no package could be found - normally it is existing but masked.""" + + if cpv in self.deps[type]: + return # in list already and therefore it's already in the tree too + + # try to find an already installed instance + update = False + downgrade = False + uVersion = None + changedUse = [] + try: + pkg = self._get_pkg_from_cpv(cpv, unmask) + if not pkg.is_installed(): + old = system.find_packages(pkg.get_slot_cp(), system.SET_INSTALLED) + if old: + old = old[0] # assume we have only one there + cmp = pkg.compare_version(old) + if cmp > 0: + update = True + elif cmp < 0: + downgrade = True + + uVersion = old.get_version() + + old_iuse = set(old.get_iuse_flags()) + new_iuse = set(pkg.get_iuse_flags()) + + for i in old_iuse.difference(new_iuse): + changedUse.append("-"+i) + + for i in new_iuse.difference(old_iuse): + changedUse.append("+"+i) + else: + old_iuse = set(pkg.get_iuse_flags(installed = True)) + new_iuse = set(pkg.get_iuse_flags(installed = False)) + + for i in old_iuse.difference(new_iuse): + changedUse.append("-"+i) + + for i in new_iuse.difference(old_iuse): + changedUse.append("+"+i) + + except backend.PackageNotFoundException, e: # package not found / package is masked -> delete current tree and re-raise the exception + if type == "update": # remove complete tree + self.remove_with_children(self.tree.first_iter(it), removeNewFlags = False) + + elif type == "install": # remove only the intentionally added package + top = self.tree.first_iter(it) + parent = self.tree.parent_iter(it) + + if parent: + while not self.tree.iter_equal(top, parent): + parent = self.tree.parent_iter(parent) + it = self.tree.parent_iter(it) + + self.remove_with_children(it, removeNewFlags = False) + + if not self.tree.iter_has_children(top): # remove completely if nothing left + self.remove(top) + raise + + # add iter + subIt = self.tree.append(it, self.tree.build_append_value(cpv, oneshot = oneshot, update = update, downgrade = downgrade, version = uVersion, useChange = changedUse)) + self.iters[type][cpv] = subIt + + # get dependencies + deps = pkg.get_dep_packages(return_blocks = True) + self.deps[type][cpv] = deps + + for d in deps: + if d[0] == "!": # block + dep = d[1:] + if not dep in self.blocks[type]: + self.blocks[type][dep] = set() + + self.blocks[type][dep].add(cpv) + else: # recursive call + self.update_tree(subIt, d, unmask, type = type) + + def append (self, cpv, type = "install", update = False, forceUpdate = False, unmask = False, oneshot = False): + """Appends a cpv either to the merge queue or to the unmerge-queue. + Also updates the tree-view. + + @param cpv: Package to add + @type cpv: string (cat/pkg-ver) + @param type: The type of this append process. Possible values are "install", "uninstall", "update". + @type type: string + @param update: Set to True if a package is going to be updated (e.g. if the use-flags changed). + @type update: boolean + @param forceUpdate: Set to True if the update should be forced. + @type forceUpdate: boolean + @param unmask: True if we are allowed to look for masked packages + @type unmask: boolean + @param oneshot: True if this package should not be added to the world-file. + @type oneshot: boolean + + @raises portato.backend.PackageNotFoundException: if trying to add a package which does not exist""" + + if type in ("install", "update"): # emerge + if update: + pkg = self._get_pkg_from_cpv(cpv, unmask) + deps = pkg.get_dep_packages(return_blocks = True) + + if not forceUpdate and cpv in self.deps[type] and deps == self.deps[type][cpv]: + return # nothing changed - return + else: + hasBeenInQueue = (cpv in self.mergequeue or cpv in self.oneshotmerge) + parentIt = self.tree.parent_iter(self.iters[type][cpv]) + + # delete it out of the tree - but NOT the changed flags + self.remove_with_children(self.iters[type][cpv], removeNewFlags = False) + + if hasBeenInQueue: # package has been in queue before + self._queue_append(cpv, oneshot) + + self.update_tree(parentIt, cpv, unmask, oneshot = oneshot, type = type) + else: # not update + if type == "install": + 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) + + # handle blocks + if self.blocks[type]: + # check whether anything blocks something in the queue + for block in self.blocks[type]: + for c in self.iters[type]: + if system.cpv_matches(c, block): + blocked = ", ".join(self.blocks[type][block]) + warning("'%s' is blocked by: %s", c, blocked) + self.remove_with_children(self.iters[type][c], False) + raise BlockedException(c, blocked) + + # + # check whether we block a version that we are going to replace nevertheless + # + + # get the blocks that block an installed package + inst = [] + for block in self.blocks[type]: + pkgs = system.find_packages(block, system.SET_INSTALLED) + if pkgs: + inst.append((pkgs, block)) + + # the slot-cp's of the packages in the queue + slots = {} + for c in self.iters[type]: + slots[system.new_package(c).get_slot_cp()] = cpv + + # check the installed blocks against the slot-cp's + for pkgs, block in inst[:]: + done = False + for pkg in pkgs: + done = False + if pkg.get_slot_cp() in slots: + debug("Block '%s' can be ignored, because the blocking package is going to be replaced with '%s'.", block, slots[pkg.get_slot_cp()]) + done = True + if done: + inst.remove((pkgs,block)) + + if inst: # there is still something left to block + for pkgs, block in inst: + blocked = ", ".join(self.blocks[type][block]) + warning("'%s' blocks the installation of: %s", pkgs[0].get_cpv(), blocked) + self.remove_with_children(self.iters[type][cpv], False) + raise BlockedException(blocked, pkgs[0].get_cpv()) + + else: # unmerge + self.unmergequeue.append(cpv) + if self.tree: # update tree + self.iters["uninstall"].update({cpv: self.tree.append(self.tree.get_unmerge_it(), self.tree.build_append_value(cpv))}) + + def _queue_append (self, cpv, oneshot = False): + """Convenience function appending a cpv either to self.mergequeue or to self.oneshotmerge. + + @param cpv: cpv to add + @type cpv: string (cpv) + @param oneshot: True if this package should not be added to the world-file. + @type oneshot: boolean""" + + if not oneshot: + if cpv not in self.mergequeue: + self.mergequeue.append(cpv) + else: + if cpv not in self.oneshotmerge: + self.oneshotmerge.append(cpv) + + def doEmerge (self, options, packages, it, *args, **kwargs): + top = None + if self.tree and it: + for v in it.itervalues(): + self.tree.set_in_progress(v) + top = self.tree.first_iter(v) + break + + self.threadQueue.put(self.__emerge, options, packages, it, top, *args, **kwargs) + + def __emerge (self, options, packages, it, top, command = None): + """Calls emerge and updates the terminal. + + @param options: options to send to emerge + @type options: string[] + @param packages: packages to emerge + @type packages: string[] + @param it: Iterators which point to these entries whose children will be removed after completion. + @type it: dict(string -> Iterator) + @param top: The top iterator + @type top: Iterator + @param command: the command to execute - default is "/usr/bin/python /usr/bin/emerge" + @type command: string[]""" + + @plugin.hook("emerge", packages = packages, command = command, console = self.console, title_update = self.title_update) + def sub_emerge(command): + if command is None: + command = system.get_merge_command() + + # open tty + if self.console: + self.console.reset() + + def pre (): + os.setsid() # new session + if self.console: + import fcntl, termios + fcntl.ioctl(self.pty[1], termios.TIOCSCTTY, 0) # set pty-slave as session tty + os.dup2(self.pty[1], 0) + os.dup2(self.pty[1], 1) + os.dup2(self.pty[1], 2) + + # get all categories that are being touched during the emerge process + cats = set(map(lambda x: x.split("/")[0], it.iterkeys())) + + # start emerge + self.process = Popen(command+options+packages, shell = False, env = system.get_environment(), preexec_fn = pre) + + # remove packages from queue + if self.tree and it and not self.tree.is_in_unmerge(top): + self.up = Updater(self, it, self.threadClass) + else: + self.up = None + + # update title + if self.console: + old_title = self.console.get_window_title() + while self.process and self.process.poll() is None: + if self.title_update : + title = self.console.get_window_title() + if title != old_title: + self.title_update(title) + old_title = title + time.sleep(0.5) + + if self.up: + self.up.stop() + if it: + self.tree.set_in_progress(top, False) + else: + self.remove(top) + elif self.tree and it: + self.remove_with_children(top) + + if self.title_update: self.title_update(None) + + if self.process is None: # someone resetted this + self.threadQueue.next() + return + else: + ret = self.process.returncode + self.process = None + self.threadQueue.next() + + @plugin.hook("after_emerge", packages = packages, retcode = ret) + def update_packages(): + if self.db: + for cat in cats: + self.db.reload(cat) + debug("Category %s refreshed", cat) + + update_packages() + + sub_emerge(command) + + def emerge (self, force = False, options = None): + """Emerges everything in the merge-queue. + + @param force: If False, '-pv' is send to emerge. Default: False. + @type force: boolean + @param options: Additional options to send to the emerge command + @type options: string[]""" + + def prepare(queue): + """Prepares the list of iterators and the list of packages.""" + list = [] + its = {} + for k in queue: + list += ["="+k] + if self.tree: + its.update({k : self.iters["install"][k]}) + + return list, its + + if self.tree: + ownit = self.iters["install"] + else: + ownit = {} + + # oneshot-queue + if self.oneshotmerge: + # prepare package-list for oneshot + list, its = prepare(self.oneshotmerge) + if not self.mergequeue :# the other one does not exist - remove completely + its = ownit + + s = system.get_oneshot_option() + if not force: s += system.get_pretend_option() + if options is not None: s += options + + self.doEmerge(s, list, its, caller = self.emerge) + + # normal queue + if self.mergequeue: + # prepare package-list + list, its = prepare(self.mergequeue) + if not self.oneshotmerge: # the other one does not exist - remove completely + its = ownit + + s = [] + if not force: s = system.get_pretend_option() + if options is not None: s += options + + self.doEmerge(s, list, its, caller = self.emerge) + + def unmerge (self, force = False, options = None): + """Unmerges everything in the umerge-queue. + + @param force: If False, '-pv' is send to emerge. Default: False. + @type force: boolean + @param options: Additional options to send to the emerge command + @type options: string[]""" + + if len(self.unmergequeue) == 0: return # nothing in queue + + list = self.unmergequeue[:] # copy the unmerge-queue + + # set options + s = system.get_unmerge_option() + if not force: s += system.get_pretend_option() + if options is not None: s += options + + if self.tree: + it = self.iters["uninstall"] + else: + it = {} + + self.doEmerge(s,list, it, caller = self.unmerge) + + def update_world(self, sets = ("world",), force = False, newuse = False, deep = False, options = None): + """Does an update world. newuse and deep are the arguments handed to emerge. + + @param sets: The sets to update. + @type sets: string[] + @param force: If False, '-pv' is send to emerge. Default: False. + @type force: boolean + @param newuse: If True, append newuse options + @type newuse: boolean + @param deep: If True, append deep options + @type deep: boolean + @param options: Additional options to send to the emerge command + @type options: string[]""" + + opts = system.get_update_option() + + if newuse: opts += system.get_newuse_option() + if deep: opts += system.get_deep_option() + if not force: opts += system.get_pretend_option() + if options is not None: opts += options + + if self.tree: + it = self.iters["update"] + else: + it = {} + + self.doEmerge(opts, list(sets), it, caller = self.update_world) + + def sync (self, command = None): + """Calls "emerge --sync". + + @param command: command to execute to sync. If None "emerge --sync" is taken. + @type command: string[]""" + + if command is None: + command = system.get_sync_command() + + try: + while True: + idx = command.index("&&") + self.doEmerge([],[],{}, command[:idx], caller = self.sync) + command = command[idx+1:] + except ValueError: # no && in command + self.doEmerge([],[],{}, command, caller = self.sync) + + def kill_emerge (self): + """Kills the emerge process.""" + if self.process is not None: + self.threadQueue.clear() # remove all pending emerge threads + try: + pgid = os.getpgid(self.process.pid) + os.killpg(pgid, signal.SIGTERM) + debug("Process should be terminated") + if self.process.poll() is None: + os.killpg(pgid, signal.SIGKILL) + debug("Process should be killed") + except AttributeError: + debug("AttributeError occured ==> process not exisiting - ignore") + except OSError: + debug("OSError occured ==> process already stopped - ignore") + + self.process = None + + def stop_emerge (self): + if self.process is not None: + os.killpg(os.getpgid(self.process.pid), signal.SIGSTOP) + debug("Process should be stopped") + + def continue_emerge (self): + if self.process is not None: + os.killpg(os.getpgid(self.process.pid), signal.SIGCONT) + debug("Process should continue") + + def remove_with_children (self, it, removeNewFlags = True): + """Convenience function which removes all children of an iterator and than the iterator itself. + + @param it: The iter which to remove. + @type it: Iterator + @param removeNewFlags: True if new flags should be removed; False otherwise. Default: True. + @type removeNewFlags: boolean""" + + self.remove_children(it, removeNewFlags) + self.remove(it, removeNewFlags) + + def remove_children (self, parentIt, removeNewFlags = True): + """Removes all children of a given parent TreeIter recursivly. + + @param parentIt: The iter from which to remove all children. + @type parentIt: Iterator + @param removeNewFlags: True if new flags should be removed; False otherwise. Default: True. + @type removeNewFlags: boolean""" + + childIt = self.tree.first_child_iter(parentIt) + + while childIt: + if (self.tree.iter_has_children(childIt)): # recursive call + self.remove_children(childIt, removeNewFlags) + temp = childIt + childIt = self.tree.next_iter(childIt) + self.remove(temp, removeNewFlags) + + def remove (self, it, removeNewFlags = True): + """Removes a specific item in the tree. This does not remove the top-entries. + + @param it: Iterator which points to the entry we are going to remove. + @type it: Iterator + @param removeNewFlags: True if new flags should be removed; False otherwise. Default: True. + @type removeNewFlags: boolean""" + + def __remove (type, cpv): + del self.iters[type][cpv] + try: + del self.deps[type][cpv] + except KeyError: # this seems to be removed due to a BlockedException - so no deps here atm ;) + debug("Catched KeyError => %s seems not to be in self.deps. Should be no harm in normal cases.", cpv) + + for key in self.blocks[type].keys(): + if cpv in self.blocks[type][key]: + self.blocks[type][key].remove(cpv) + + if not self.blocks[type][key]: # list is empty -> remove the whole key + del self.blocks[type][key] + + if removeNewFlags: # remove the changed flags + flags.remove_new_use_flags(cpv) + flags.remove_new_masked(cpv) + flags.remove_new_testing(cpv) + + if self.tree.iter_has_parent(it): + cpv = self.tree.get_value(it, self.tree.get_cpv_column()) + if self.tree.is_in_emerge(it): # Emerge + + __remove("install", cpv) + + try: + self.mergequeue.remove(cpv) + except ValueError: # this is a dependency - ignore + try: + self.oneshotmerge.remove(cpv) + except ValueError: + debug("Catched ValueError => %s seems not to be in merge-queue. Should be no harm.", cpv) + + elif self.tree.is_in_unmerge(it): # in Unmerge + del self.iters["uninstall"][cpv] + self.unmergequeue.remove(cpv) + + elif self.tree.is_in_update(it): + __remove("update", cpv) + + + self.tree.remove(it) + + def is_empty (self): + """Checks whether the current queue is empty and not working. Therefore it looks, whether the queues are empty, + and the process is not running. + + @returns: True if everything is empty and the process is not running. + @rtype: bool""" + + return not (self.process or any(map(len, self.iters.itervalues()))) diff --git a/portato/gui/session.py b/portato/gui/session.py index a37cd85..1eca248 100644 --- a/portato/gui/session.py +++ b/portato/gui/session.py @@ -18,16 +18,16 @@ SESSION_VERSION = 1 class SessionException (Exception): - error = _("Version mismatch.") - def __init__ (self, got, expected): - self.got = got - self.expected = expected + error = _("Version mismatch.") + def __init__ (self, got, expected): + self.got = got + self.expected = expected - def __str__ (self): - return "%s %s" % (self.error, (_("Got '%d' - expected '%d'.") % (self.got, self.expected))) + def __str__ (self): + return "%s %s" % (self.error, (_("Got '%d' - expected '%d'.") % (self.got, self.expected))) class OldSessionException (SessionException): - error = _("Current session format is too old.") + error = _("Current session format is too old.") class NewSessionException (SessionException): - error = _("Current session format is newer than this version supports.") + error = _("Current session format is newer than this version supports.") diff --git a/portato/gui/updater.py b/portato/gui/updater.py index c4c81e7..c4ad2a5 100644 --- a/portato/gui/updater.py +++ b/portato/gui/updater.py @@ -18,109 +18,109 @@ import threading, subprocess, time from ..helper import debug, warning, error class Updater (object): - """ - This class is intended to check what package is currently being installed and remove this one from the queue. + """ + This class is intended to check what package is currently being installed and remove this one from the queue. - @cvar SED_EXP: The sed expression to strip the package name out of the qlop call. - """ - - SED_EXP = r""" + @cvar SED_EXP: The sed expression to strip the package name out of the qlop call. + """ + + SED_EXP = r""" /\*/{ s/ \* // n } d""" - - def __init__ (self, queue, iterators, threadClass = threading.Thread): - """ - Constructor. - Also directly initializes the thread. - - @param queue: an emerge queue instance - @type queue: EmergeQueue - @param iterators: a dictionary of iterators in the current queue - @type iterators: dict(string->Iterator) - """ - - if not issubclass(threadClass, threading.Thread): - raise ValueError, "Only subclasses of threading.Thread are allowed." - - self.queue = queue - self.iterators = iterators - self.threadClass = threadClass - self.stopEvent = threading.Event() - self.removed = set() - - t = threadClass(name = "Queue Updater Thread", target = self.run) - t.setDaemon(True) - t.start() - - def run (self): - """ - Run and run and run ... - Checks the packages until being stopped. - """ - - curr = set() - while not self.stopEvent.isSet(): - - # this = $(qlop -cCq | sed $SED_EXP) - p1 = subprocess.Popen(["qlop", "--current", "--nocolor", "--quiet"], stdout = subprocess.PIPE) - this = subprocess.Popen(["sed", self.SED_EXP], stdout = subprocess.PIPE, stdin = p1.stdout).communicate()[0] - - this = set(this.split()) if this else set() - for removed in curr - this: - self.remove(self.find(removed)) # remove the previous - curr = this - - time.sleep(2.0) - - self.removed = set() - - def stop (self): - """ - Stops the current updater. - """ - self.stopEvent.set() - - def find (self, pv, masked = False): - """ - As qlop only returns 'package-version' we need to assign it to a cpv. - This is done here. - """ - - pkgs = system.find_packages("=%s" % pv, only_cpv = True, masked = masked) - - if len(pkgs) > 1: # ambigous - try to find the one which is also in the iterators - for p in pkgs: - if p in self.iterators: - return p - elif not pkgs: # nothing found =| - if not masked: - warning(_("No unmasked version of package '%s' found. Trying masked ones. This normally should not happen..."), pv) - return self.find(pv, True) - - else: - error(_("Trying to remove package '%s' from queue which does not exist in system."), pv) - return None - else: # only one choice =) - return pkgs[0] - - def remove (self, cpv): - """ - Remove a package from the queue. - """ - - if cpv is None: - debug("Nothing to remove.") - return - - if cpv in self.removed: - return - - self.removed.add(cpv) - - try: - self.queue.remove_with_children(self.iterators[cpv]) - except KeyError: - debug("'%s' should be removed, but is not in queue.", cpv) + + def __init__ (self, queue, iterators, threadClass = threading.Thread): + """ + Constructor. + Also directly initializes the thread. + + @param queue: an emerge queue instance + @type queue: EmergeQueue + @param iterators: a dictionary of iterators in the current queue + @type iterators: dict(string->Iterator) + """ + + if not issubclass(threadClass, threading.Thread): + raise ValueError, "Only subclasses of threading.Thread are allowed." + + self.queue = queue + self.iterators = iterators + self.threadClass = threadClass + self.stopEvent = threading.Event() + self.removed = set() + + t = threadClass(name = "Queue Updater Thread", target = self.run) + t.setDaemon(True) + t.start() + + def run (self): + """ + Run and run and run ... + Checks the packages until being stopped. + """ + + curr = set() + while not self.stopEvent.isSet(): + + # this = $(qlop -cCq | sed $SED_EXP) + p1 = subprocess.Popen(["qlop", "--current", "--nocolor", "--quiet"], stdout = subprocess.PIPE) + this = subprocess.Popen(["sed", self.SED_EXP], stdout = subprocess.PIPE, stdin = p1.stdout).communicate()[0] + + this = set(this.split()) if this else set() + for removed in curr - this: + self.remove(self.find(removed)) # remove the previous + curr = this + + time.sleep(2.0) + + self.removed = set() + + def stop (self): + """ + Stops the current updater. + """ + self.stopEvent.set() + + def find (self, pv, masked = False): + """ + As qlop only returns 'package-version' we need to assign it to a cpv. + This is done here. + """ + + pkgs = system.find_packages("=%s" % pv, only_cpv = True, masked = masked) + + if len(pkgs) > 1: # ambigous - try to find the one which is also in the iterators + for p in pkgs: + if p in self.iterators: + return p + elif not pkgs: # nothing found =| + if not masked: + warning(_("No unmasked version of package '%s' found. Trying masked ones. This normally should not happen..."), pv) + return self.find(pv, True) + + else: + error(_("Trying to remove package '%s' from queue which does not exist in system."), pv) + return None + else: # only one choice =) + return pkgs[0] + + def remove (self, cpv): + """ + Remove a package from the queue. + """ + + if cpv is None: + debug("Nothing to remove.") + return + + if cpv in self.removed: + return + + self.removed.add(cpv) + + try: + self.queue.remove_with_children(self.iterators[cpv]) + except KeyError: + debug("'%s' should be removed, but is not in queue.", cpv) diff --git a/portato/gui/utils.py b/portato/gui/utils.py index cd5c50c..661af6b 100644 --- a/portato/gui/utils.py +++ b/portato/gui/utils.py @@ -32,280 +32,280 @@ from ..constants import APP, LOCALE_DIR from ..config_parser import ConfigParser def get_color (cfg, name): - return gtk.gdk.color_parse("#%s" % cfg.get(name, section = "COLORS")) + return gtk.gdk.color_parse("#%s" % cfg.get(name, section = "COLORS")) class GtkThread (Thread): - def run(self): - # for some reason, I have to install this for each thread ... - gettext.install(APP, LOCALE_DIR, unicode = True) - try: - Thread.run(self) - except SystemExit: - raise # let normal thread handle it - except: - type, val, tb = sys.exc_info() - try: - sys.excepthook(type, val, tb, thread = self.getName()) - except TypeError: - raise type, val, tb # let normal thread handle it - finally: - del type, val, tb + def run(self): + # for some reason, I have to install this for each thread ... + gettext.install(APP, LOCALE_DIR, unicode = True) + try: + Thread.run(self) + except SystemExit: + raise # let normal thread handle it + except: + type, val, tb = sys.exc_info() + try: + sys.excepthook(type, val, tb, thread = self.getName()) + except TypeError: + raise type, val, tb # let normal thread handle it + finally: + del type, val, tb class Config (ConfigParser): - - def __init__ (self, cfgFile): - """Constructor. - - @param cfgFile: path to config file - @type cfgFile: string""" - - ConfigParser.__init__(self, cfgFile) - - # read config - self.parse() - - # local configs - self.local = {} - - def modify_flags_config (self): - """Sets the internal config of the L{flags}-module. - @see: L{flags.set_config()}""" - - flagCfg = { - "usefile": self.get("useFile"), - "usePerVersion" : self.get_boolean("usePerVersion"), - "maskfile" : self.get("maskFile"), - "maskPerVersion" : self.get_boolean("maskPerVersion"), - "testingfile" : self.get("keywordFile"), - "testingPerVersion" : self.get_boolean("keywordPerVersion")} - flags.set_config(flagCfg) - - def modify_debug_config (self): - if self.get_boolean("debug"): - level = logging.DEBUG - else: - level = logging.INFO - - set_log_level(level) - - def modify_system_config (self): - """Sets the system config. - @see: L{backend.set_system()}""" - set_system(self.get("system")) - - def modify_external_configs (self): - """Convenience function setting all external configs.""" - self.modify_debug_config() - self.modify_flags_config() - self.modify_system_config() - - def set_local(self, cpv, name, val): - """Sets some local config. - - @param cpv: the cpv describing the package for which to set this option - @type cpv: string (cpv) - @param name: the option's name - @type name: string - @param val: the value to set - @type val: any""" - - if not cpv in self.local: - self.local[cpv] = {} - - self.local[cpv].update({name:val}) - - def get_local(self, cpv, name): - """Returns something out of the local config. - - @param cpv: the cpv describing the package from which to get this option - @type cpv: string (cpv) - @param name: the option's name - @type name: string - @return: value stored for the cpv and name or None if not found - @rtype: any""" - - if not cpv in self.local: - return None - if not name in self.local[cpv]: - return None - - return self.local[cpv][name] - - def write(self): - """Writes to the config file and modify any external configs.""" - ConfigParser.write(self) - self.modify_external_configs() + + def __init__ (self, cfgFile): + """Constructor. + + @param cfgFile: path to config file + @type cfgFile: string""" + + ConfigParser.__init__(self, cfgFile) + + # read config + self.parse() + + # local configs + self.local = {} + + def modify_flags_config (self): + """Sets the internal config of the L{flags}-module. + @see: L{flags.set_config()}""" + + flagCfg = { + "usefile": self.get("useFile"), + "usePerVersion" : self.get_boolean("usePerVersion"), + "maskfile" : self.get("maskFile"), + "maskPerVersion" : self.get_boolean("maskPerVersion"), + "testingfile" : self.get("keywordFile"), + "testingPerVersion" : self.get_boolean("keywordPerVersion")} + flags.set_config(flagCfg) + + def modify_debug_config (self): + if self.get_boolean("debug"): + level = logging.DEBUG + else: + level = logging.INFO + + set_log_level(level) + + def modify_system_config (self): + """Sets the system config. + @see: L{backend.set_system()}""" + set_system(self.get("system")) + + def modify_external_configs (self): + """Convenience function setting all external configs.""" + self.modify_debug_config() + self.modify_flags_config() + self.modify_system_config() + + def set_local(self, cpv, name, val): + """Sets some local config. + + @param cpv: the cpv describing the package for which to set this option + @type cpv: string (cpv) + @param name: the option's name + @type name: string + @param val: the value to set + @type val: any""" + + if not cpv in self.local: + self.local[cpv] = {} + + self.local[cpv].update({name:val}) + + def get_local(self, cpv, name): + """Returns something out of the local config. + + @param cpv: the cpv describing the package from which to get this option + @type cpv: string (cpv) + @param name: the option's name + @type name: string + @return: value stored for the cpv and name or None if not found + @rtype: any""" + + if not cpv in self.local: + return None + if not name in self.local[cpv]: + return None + + return self.local[cpv][name] + + def write(self): + """Writes to the config file and modify any external configs.""" + ConfigParser.write(self) + self.modify_external_configs() class PkgData (object): - __slots__ = ("cat", "pkg", "inst") + __slots__ = ("cat", "pkg", "inst") - def __init__ (self, cat, pkg, inst): - self.cat = cat - self.pkg = pkg - self.inst = inst + def __init__ (self, cat, pkg, inst): + self.cat = cat + self.pkg = pkg + self.inst = inst - def __iter__ (self): - return iter((self.cat, self.pkg, self.inst)) + def __iter__ (self): + return iter((self.cat, self.pkg, self.inst)) - def __cmp__ (self, other): - return cmp(self.pkg.lower(), other.pkg.lower()) + def __cmp__ (self, other): + return cmp(self.pkg.lower(), other.pkg.lower()) - def __repr__ (self): - return "" % {"cat" : self.cat, "pkg" : self.pkg, "inst" : self.inst} + def __repr__ (self): + return "" % {"cat" : self.cat, "pkg" : self.pkg, "inst" : self.inst} class Database (object): - """An internal database which holds a simple dictionary cat -> [package_list].""" - - ALL = _("ALL") - - def __init__ (self): - """Constructor.""" - self.__initialize() - self._lock = RLock() - - def lock (f): - @wraps(f) - def wrapper (self, *args, **kwargs): - with self._lock: - r = f(self, *args, **kwargs) - return r - - return wrapper - - def __initialize (self): - self._db = defaultdict(list) - self.inst_cats = set([self.ALL]) - self._restrict = None - - def __sort_key (self, x): - return x.pkg.lower() - - @lock - def populate (self, category = None): - """Populates the database. - - @param category: An optional category - so only packages of this category are inserted. - @type category: string - """ - - # get the lists - packages = system.find_packages(category, with_version = False) - installed = system.find_packages(category, system.SET_INSTALLED, with_version = False) - - # cycle through packages - for p in packages: - cat, pkg = p.split("/") - inst = p in installed - t = PkgData(cat, pkg, inst) - self._db[cat].append(t) - self._db[self.ALL].append(t) - - if inst: - self.inst_cats.add(cat) - - for key in self._db: # sort alphabetically - self._db[key].sort(key = self.__sort_key) - - @lock - def get_cat (self, cat = None, byName = True): - """Returns the packages in the category. - - @param cat: category to return the packages from; if None it defaults to "ALL" - @type cat: string - @param byName: selects whether to return the list sorted by name or by installation - @type byName: boolean - @return: an iterator over a list of tuples: (category, name, is_installed) or [] - @rtype: (string, string, boolean) - """ - - if not cat: - cat = self.ALL - - def get_pkgs(): - if byName: - for pkg in self._db[cat]: - yield pkg - else: - ninst = [] - for pkg in self._db[cat]: - if pkg.inst: - yield pkg - else: - ninst.append(pkg) - - for pkg in ninst: - yield pkg - - try: - if self.restrict: - return (pkg for pkg in get_pkgs() if self.restrict.search(pkg.pkg))#if pkg[1].find(self.restrict) != -1) - else: - return get_pkgs() - - except KeyError: # cat is in category list - but not in portage - info(_("Catched KeyError => %s seems not to be an available category. Have you played with rsync-excludes?"), cat) - - @lock - def get_categories (self, installed = False): - """Returns all categories. - - @param installed: Only return these with at least one installed package. - @type installed: boolean - @returns: the list of categories - @rtype: string - """ - - if not self.restrict: - if installed: - cats = self.inst_cats - else: - cats = self._db.iterkeys() - - else: - if installed: - cats = set((pkg.cat for pkg in self.get_cat(self.ALL) if pkg.inst)) - else: - cats = set((pkg.cat for pkg in self.get_cat(self.ALL))) - - if len(cats)>1: - cats.add(self.ALL) - - return (cat for cat in cats) - - @lock - def reload (self, cat = None): - """Reloads the given category. - - @param cat: category - @type cat: string - """ - - if cat: - del self._db[cat] - try: - self.inst_cats.remove(cat) - except KeyError: # not in inst_cats - can be ignored - pass - - self._db[self.ALL] = filter(lambda x: x.cat != cat, self._db[self.ALL]) - self.populate(cat+"/*") - else: - self.__initialize() - self.populate() - - def get_restrict (self): - return self._restrict - - @lock - def set_restrict (self, restrict): - if not restrict: - self._restrict = None - else: - try: - regex = re.compile(restrict, re.I) - except re.error, e: - info(_("Error while compiling search expression: '%s'."), str(e)) - else: # only set self._restrict if no error occurred - self._restrict = regex - - restrict = property(get_restrict, set_restrict) + """An internal database which holds a simple dictionary cat -> [package_list].""" + + ALL = _("ALL") + + def __init__ (self): + """Constructor.""" + self.__initialize() + self._lock = RLock() + + def lock (f): + @wraps(f) + def wrapper (self, *args, **kwargs): + with self._lock: + r = f(self, *args, **kwargs) + return r + + return wrapper + + def __initialize (self): + self._db = defaultdict(list) + self.inst_cats = set([self.ALL]) + self._restrict = None + + def __sort_key (self, x): + return x.pkg.lower() + + @lock + def populate (self, category = None): + """Populates the database. + + @param category: An optional category - so only packages of this category are inserted. + @type category: string + """ + + # get the lists + packages = system.find_packages(category, with_version = False) + installed = system.find_packages(category, system.SET_INSTALLED, with_version = False) + + # cycle through packages + for p in packages: + cat, pkg = p.split("/") + inst = p in installed + t = PkgData(cat, pkg, inst) + self._db[cat].append(t) + self._db[self.ALL].append(t) + + if inst: + self.inst_cats.add(cat) + + for key in self._db: # sort alphabetically + self._db[key].sort(key = self.__sort_key) + + @lock + def get_cat (self, cat = None, byName = True): + """Returns the packages in the category. + + @param cat: category to return the packages from; if None it defaults to "ALL" + @type cat: string + @param byName: selects whether to return the list sorted by name or by installation + @type byName: boolean + @return: an iterator over a list of tuples: (category, name, is_installed) or [] + @rtype: (string, string, boolean) + """ + + if not cat: + cat = self.ALL + + def get_pkgs(): + if byName: + for pkg in self._db[cat]: + yield pkg + else: + ninst = [] + for pkg in self._db[cat]: + if pkg.inst: + yield pkg + else: + ninst.append(pkg) + + for pkg in ninst: + yield pkg + + try: + if self.restrict: + return (pkg for pkg in get_pkgs() if self.restrict.search(pkg.pkg))#if pkg[1].find(self.restrict) != -1) + else: + return get_pkgs() + + except KeyError: # cat is in category list - but not in portage + info(_("Catched KeyError => %s seems not to be an available category. Have you played with rsync-excludes?"), cat) + + @lock + def get_categories (self, installed = False): + """Returns all categories. + + @param installed: Only return these with at least one installed package. + @type installed: boolean + @returns: the list of categories + @rtype: string + """ + + if not self.restrict: + if installed: + cats = self.inst_cats + else: + cats = self._db.iterkeys() + + else: + if installed: + cats = set((pkg.cat for pkg in self.get_cat(self.ALL) if pkg.inst)) + else: + cats = set((pkg.cat for pkg in self.get_cat(self.ALL))) + + if len(cats)>1: + cats.add(self.ALL) + + return (cat for cat in cats) + + @lock + def reload (self, cat = None): + """Reloads the given category. + + @param cat: category + @type cat: string + """ + + if cat: + del self._db[cat] + try: + self.inst_cats.remove(cat) + except KeyError: # not in inst_cats - can be ignored + pass + + self._db[self.ALL] = filter(lambda x: x.cat != cat, self._db[self.ALL]) + self.populate(cat+"/*") + else: + self.__initialize() + self.populate() + + def get_restrict (self): + return self._restrict + + @lock + def set_restrict (self, restrict): + if not restrict: + self._restrict = None + else: + try: + regex = re.compile(restrict, re.I) + except re.error, e: + info(_("Error while compiling search expression: '%s'."), str(e)) + else: # only set self._restrict if no error occurred + self._restrict = regex + + restrict = property(get_restrict, set_restrict) diff --git a/portato/gui/views.py b/portato/gui/views.py index bd98ad8..a2d0468 100644 --- a/portato/gui/views.py +++ b/portato/gui/views.py @@ -20,130 +20,130 @@ import logging from ..helper import warning class LazyView (object): - def __init__ (self): - self.connect("map", self.cb_mapped) + def __init__ (self): + self.connect("map", self.cb_mapped) - self.pkg = None - self.updated = False + self.pkg = None + self.updated = False - def update (self, pkg, force = False): - self.pkg = pkg - self.updated = True - - if force: - self.cb_mapped() + def update (self, pkg, force = False): + self.pkg = pkg + self.updated = True + + if force: + self.cb_mapped() - def cb_mapped (self, *args): - if self.updated and self.pkg: - self.set_text("".join(self._get_content())) - self.updated = False + def cb_mapped (self, *args): + if self.updated and self.pkg: + self.set_text("".join(self._get_content())) + self.updated = False - return False + return False - def set_text (self, text): - raise NotImplementedError + def set_text (self, text): + raise NotImplementedError - def _get_content (self): - raise NotImplementedError + def _get_content (self): + raise NotImplementedError class ListView (gtk.TextView, LazyView): - def __init__ (self, content_fn): - self.content_fn = content_fn + def __init__ (self, content_fn): + self.content_fn = content_fn - gtk.TextView.__init__(self) - LazyView.__init__(self) + gtk.TextView.__init__(self) + LazyView.__init__(self) - self.set_editable(False) - self.set_cursor_visible(False) + self.set_editable(False) + self.set_cursor_visible(False) - def set_text (self, text): - self.get_buffer().set_text(text) + def set_text (self, text): + self.get_buffer().set_text(text) - def _get_content (self): - return self.content_fn(self.pkg) + def _get_content (self): + return self.content_fn(self.pkg) class InstalledOnlyView (ListView): - def _get_content (self): - if self.pkg: - if not self.pkg.is_installed(): - return _("Package is not installed") - else: - return ListView._get_content(self) - else: - return "Huh?" + def _get_content (self): + if self.pkg: + if not self.pkg.is_installed(): + return _("Package is not installed") + else: + return ListView._get_content(self) + else: + return "Huh?" class HighlightView (gtksourceview2.View, LazyView): - def __init__ (self, get_file_fn, languages = []): - self.get_fn = get_file_fn - - man = gtksourceview2.LanguageManager() - - language = None - old_lang = None - for lang in languages: - if old_lang: - warning(_("No %(old)s language file installed. Falling back to %(new)s."), {"old" : old_lang, "new" : lang}) - - language = man.get_language(lang) - if language: - break - else: - old_lang = lang - - if not language and old_lang: - warning(_("No %(old)s language file installed. Disable highlighting."), {"old" : old_lang}) - - buf = gtksourceview2.Buffer() - buf.set_language(language) - - gtksourceview2.View.__init__(self, buf) - LazyView.__init__(self) - - self.set_editable(False) - self.set_cursor_visible(False) - - def set_text (self, text): - self.get_buffer().set_text(text) - - def _get_content (self): - try: - with open(self.get_fn(self.pkg)) as f: - return f.readlines() - except IOError, e: - return _("Error: %s") % e.strerror - + def __init__ (self, get_file_fn, languages = []): + self.get_fn = get_file_fn + + man = gtksourceview2.LanguageManager() + + language = None + old_lang = None + for lang in languages: + if old_lang: + warning(_("No %(old)s language file installed. Falling back to %(new)s."), {"old" : old_lang, "new" : lang}) + + language = man.get_language(lang) + if language: + break + else: + old_lang = lang + + if not language and old_lang: + warning(_("No %(old)s language file installed. Disable highlighting."), {"old" : old_lang}) + + buf = gtksourceview2.Buffer() + buf.set_language(language) + + gtksourceview2.View.__init__(self, buf) + LazyView.__init__(self) + + self.set_editable(False) + self.set_cursor_visible(False) + + def set_text (self, text): + self.get_buffer().set_text(text) + + def _get_content (self): + try: + with open(self.get_fn(self.pkg)) as f: + return f.readlines() + except IOError, e: + return _("Error: %s") % e.strerror + class LogView (logging.Handler): - colors = ( - (logging.DEBUG, "debug", "blue"), - (logging.INFO, "info", "green"), - (logging.WARNING, "warning", "yellow"), - (-1, "error", "red") - ) - - def __init__ (self, view): - logging.Handler.__init__(self, logging.DEBUG) - - self.view = view - self.buf = view.get_buffer() - - # set tags - for lvl, name, color in self.colors: - self.buf.create_tag("log_%s" % name, foreground = color,weight = pango.WEIGHT_BOLD) - - logging.getLogger("portatoLogger").addHandler(self) - - def emit (self, record): - - for lvl, name, color in self.colors: - if lvl == -1 or record.levelno <= lvl: - tag = "log_%s" % name - break - - def _add(): - self.buf.insert_with_tags_by_name(self.buf.get_end_iter(), "* ", tag) - self.buf.insert(self.buf.get_end_iter(), record.getMessage()+"\n") - - gobject.idle_add(_add) # logger might be called from another thread + colors = ( + (logging.DEBUG, "debug", "blue"), + (logging.INFO, "info", "green"), + (logging.WARNING, "warning", "yellow"), + (-1, "error", "red") + ) + + def __init__ (self, view): + logging.Handler.__init__(self, logging.DEBUG) + + self.view = view + self.buf = view.get_buffer() + + # set tags + for lvl, name, color in self.colors: + self.buf.create_tag("log_%s" % name, foreground = color,weight = pango.WEIGHT_BOLD) + + logging.getLogger("portatoLogger").addHandler(self) + + def emit (self, record): + + for lvl, name, color in self.colors: + if lvl == -1 or record.levelno <= lvl: + tag = "log_%s" % name + break + + def _add(): + self.buf.insert_with_tags_by_name(self.buf.get_end_iter(), "* ", tag) + self.buf.insert(self.buf.get_end_iter(), record.getMessage()+"\n") + + gobject.idle_add(_add) # logger might be called from another thread diff --git a/portato/gui/windows/about.py b/portato/gui/windows/about.py index df724f3..21608c0 100644 --- a/portato/gui/windows/about.py +++ b/portato/gui/windows/about.py @@ -18,17 +18,17 @@ from .basic import AbstractDialog from ...constants import VERSION, APP_ICON class AboutWindow (AbstractDialog): - """A window showing the "about"-informations.""" + """A window showing the "about"-informations.""" - def __init__ (self, parent): + def __init__ (self, parent): - AbstractDialog.__init__(self, parent) + AbstractDialog.__init__(self, parent) - img = gtk.Image() - img.set_from_file(APP_ICON) + img = gtk.Image() + img.set_from_file(APP_ICON) - self.window.set_version(VERSION) - self.window.set_logo(img.get_pixbuf()) + self.window.set_version(VERSION) + self.window.set_logo(img.get_pixbuf()) - self.window.show_all() + self.window.show_all() diff --git a/portato/gui/windows/basic.py b/portato/gui/windows/basic.py index 09d0d7e..6d74858 100644 --- a/portato/gui/windows/basic.py +++ b/portato/gui/windows/basic.py @@ -27,101 +27,101 @@ gtk.glade.bindtextdomain (APP, LOCALE_DIR) gtk.glade.textdomain (APP) class WrappedTree (object): - __slots__ = ("klass", "tree", "get_widget") - def __init__ (self, klass, tree): - self.tree = tree - self.klass = klass - - def __getattribute__ (self, name): - if name in WrappedTree.__slots__: - return object.__getattribute__(self, name) - else: - return getattr(self.tree, name) - - def get_widget(self, name): - w = self.tree.get_widget(name) - if w is None: - error("Widget '%s' could not be found in class '%s'.", name, self.klass) - return w + __slots__ = ("klass", "tree", "get_widget") + def __init__ (self, klass, tree): + self.tree = tree + self.klass = klass + + def __getattribute__ (self, name): + if name in WrappedTree.__slots__: + return object.__getattribute__(self, name) + else: + return getattr(self.tree, name) + + def get_widget(self, name): + w = self.tree.get_widget(name) + if w is None: + error("Widget '%s' could not be found in class '%s'.", name, self.klass) + return w class Window (object): - def __init__ (self): - - if not hasattr(self, "__tree__"): - self.__tree__ = self.__class__.__name__ - - if not hasattr(self, "__window__"): - self.__window__ = self.__class__.__name__ - - if not hasattr(self, "__file__"): - self.__file__ = self.__class__.__name__ - - self.tree = self.get_tree(self.__tree__) - self.tree.signal_autoconnect(self) - self.window = self.tree.get_widget(self.__window__) - self.window.set_icon_from_file(APP_ICON) - - @staticmethod - def watch_cursor (func): - """This is a decorator for functions being so time consuming, that it is appropriate to show the watch-cursor. - @attention: this function relies on the gtk.Window-Object being stored as self.window""" - - @wraps(func) - def wrapper (self, *args, **kwargs): - ret = None - def cb_idle(): - try: - ret = func(self, *args, **kwargs) - finally: - self.window.window.set_cursor(None) - return False - - watch = gtk.gdk.Cursor(gtk.gdk.WATCH) - self.window.window.set_cursor(watch) - gobject.idle_add(cb_idle) - return ret - - return wrapper - - def get_tree (self, name): - return WrappedTree(self.__class__.__name__, gtk.glade.XML(os.path.join(TEMPLATE_DIR, self.__file__+".glade"), name)) + def __init__ (self): + + if not hasattr(self, "__tree__"): + self.__tree__ = self.__class__.__name__ + + if not hasattr(self, "__window__"): + self.__window__ = self.__class__.__name__ + + if not hasattr(self, "__file__"): + self.__file__ = self.__class__.__name__ + + self.tree = self.get_tree(self.__tree__) + self.tree.signal_autoconnect(self) + self.window = self.tree.get_widget(self.__window__) + self.window.set_icon_from_file(APP_ICON) + + @staticmethod + def watch_cursor (func): + """This is a decorator for functions being so time consuming, that it is appropriate to show the watch-cursor. + @attention: this function relies on the gtk.Window-Object being stored as self.window""" + + @wraps(func) + def wrapper (self, *args, **kwargs): + ret = None + def cb_idle(): + try: + ret = func(self, *args, **kwargs) + finally: + self.window.window.set_cursor(None) + return False + + watch = gtk.gdk.Cursor(gtk.gdk.WATCH) + self.window.window.set_cursor(watch) + gobject.idle_add(cb_idle) + return ret + + return wrapper + + def get_tree (self, name): + return WrappedTree(self.__class__.__name__, gtk.glade.XML(os.path.join(TEMPLATE_DIR, self.__file__+".glade"), name)) class AbstractDialog (Window): - """A class all our dialogs get derived from. It sets useful default vars and automatically handles the ESC-Button.""" - - def __init__ (self, parent): - """Constructor. - - @param parent: the parent window - @type parent: gtk.Window""" - - Window.__init__(self) - - # set parent - self.window.set_transient_for(parent) - self.parent = parent - - # catch the ESC-key - self.window.connect("key-press-event", self.cb_key_pressed) - - def cb_key_pressed (self, widget, event): - """Closes the window if ESC is pressed.""" - keyname = gtk.gdk.keyval_name(event.keyval) - if keyname == "Escape": - self.close() - return True - else: - return False - - def close (self, *args): - self.window.destroy() + """A class all our dialogs get derived from. It sets useful default vars and automatically handles the ESC-Button.""" + + def __init__ (self, parent): + """Constructor. + + @param parent: the parent window + @type parent: gtk.Window""" + + Window.__init__(self) + + # set parent + self.window.set_transient_for(parent) + self.parent = parent + + # catch the ESC-key + self.window.connect("key-press-event", self.cb_key_pressed) + + def cb_key_pressed (self, widget, event): + """Closes the window if ESC is pressed.""" + keyname = gtk.gdk.keyval_name(event.keyval) + if keyname == "Escape": + self.close() + return True + else: + return False + + def close (self, *args): + self.window.destroy() class Popup (object): - def __init__ (self, name, parent, file = "popups"): - self.tree = gtk.glade.XML(os.path.join(TEMPLATE_DIR, file+".glade"), root = name) - self.tree.signal_autoconnect(parent) - self._popup = self.tree.get_widget(name) + def __init__ (self, name, parent, file = "popups"): + self.tree = gtk.glade.XML(os.path.join(TEMPLATE_DIR, file+".glade"), root = name) + self.tree.signal_autoconnect(parent) + self._popup = self.tree.get_widget(name) - def popup (self, *args): - self._popup.popup(*args) + def popup (self, *args): + self._popup.popup(*args) diff --git a/portato/gui/windows/mailinfo.py b/portato/gui/windows/mailinfo.py index 5d0a24c..22e750a 100644 --- a/portato/gui/windows/mailinfo.py +++ b/portato/gui/windows/mailinfo.py @@ -22,69 +22,69 @@ from ...helper import debug, info from ...constants import VERSION class MailInfoWindow (AbstractDialog): - TO = "bugs@portato.necoro.net" + TO = "bugs@portato.necoro.net" - def __init__ (self, parent, tb): + def __init__ (self, parent, tb): - AbstractDialog.__init__(self, parent) - - self.tb = tb - self.window.show_all() + AbstractDialog.__init__(self, parent) + + self.tb = tb + self.window.show_all() - def set_data (self): - name = self.tree.get_widget("nameEntry").get_text() - addr = self.tree.get_widget("mailEntry").get_text() + def set_data (self): + name = self.tree.get_widget("nameEntry").get_text() + addr = self.tree.get_widget("mailEntry").get_text() - if not addr: - addr = self.TO + if not addr: + addr = self.TO - if name: - fro = "%s <%s>" % (name, addr) - else: - fro = addr + if name: + fro = "%s <%s>" % (name, addr) + else: + fro = addr - commentBuffer = self.tree.get_widget("commentEntry").get_buffer() - text = commentBuffer.get_text(*commentBuffer.get_bounds()) + commentBuffer = self.tree.get_widget("commentEntry").get_buffer() + text = commentBuffer.get_text(*commentBuffer.get_bounds()) - if text: - text += "\n\n===========\n" + if text: + text += "\n\n===========\n" - text += self.tb + text += self.tb - message = """From: %s + message = """From: %s To: %s Subject: %s %s""" % ( fro, self.TO, ("[Bug Report] Bug in Portato %s" % VERSION), text) - self.addr = addr - self.message = message - - def send (self): - try: - debug("Connecting to server") - server = smtplib.SMTP("mail.necoro.eu") - debug("Sending mail") - try: - try: - server.sendmail(self.addr, self.TO, self.message) - except smtplib.SMTPRecipientsRefused, e: - info(_("An error occurred while sending. I think we were greylisted. The error: %s") % e) - info(_("Retrying after waiting 60 seconds.")) - time.sleep(60) - server.sendmail(self.addr, self.TO, self.message) - debug("Sent") - finally: - server.quit() - except socket.error, e: - mail_failure_dialog("%s (Code: %s)" % (e.args[1], e.args[0])) - - def cb_cancel_clicked (self, *args): - - self.close() - return True - - def cb_send_clicked (self, *args): - self.set_data() - GtkThread(target = self.send, name = "Mail Send Thread").start() - self.close() - return True + self.addr = addr + self.message = message + + def send (self): + try: + debug("Connecting to server") + server = smtplib.SMTP("mail.necoro.eu") + debug("Sending mail") + try: + try: + server.sendmail(self.addr, self.TO, self.message) + except smtplib.SMTPRecipientsRefused, e: + info(_("An error occurred while sending. I think we were greylisted. The error: %s") % e) + info(_("Retrying after waiting 60 seconds.")) + time.sleep(60) + server.sendmail(self.addr, self.TO, self.message) + debug("Sent") + finally: + server.quit() + except socket.error, e: + mail_failure_dialog("%s (Code: %s)" % (e.args[1], e.args[0])) + + def cb_cancel_clicked (self, *args): + + self.close() + return True + + def cb_send_clicked (self, *args): + self.set_data() + GtkThread(target = self.send, name = "Mail Send Thread").start() + self.close() + return True diff --git a/portato/gui/windows/main.py b/portato/gui/windows/main.py index 1b7c0f0..fca4535 100644 --- a/portato/gui/windows/main.py +++ b/portato/gui/windows/main.py @@ -36,8 +36,8 @@ from ..session import SESSION_VERSION, SessionException, OldSessionException, Ne from ..wrapper import GtkTree, GtkConsole from ..views import LogView, HighlightView, InstalledOnlyView from ..dialogs import (blocked_dialog, changed_flags_dialog, io_ex_dialog, - nothing_found_dialog, queue_not_empty_dialog, remove_deps_dialog, - remove_queue_dialog, remove_updates_dialog, unmask_dialog) + nothing_found_dialog, queue_not_empty_dialog, remove_deps_dialog, + remove_queue_dialog, remove_updates_dialog, unmask_dialog) # even more GUI stuff from .basic import Window, Popup @@ -48,1744 +48,1744 @@ from .search import SearchWindow from .update import UpdateWindow class PackageTable: - """A window with data about a specfic package.""" - - def __init__ (self, main): - """Build up window contents. - - @param main: the main window - @type main: MainWindow""" - - self.main = main - self.tree = main.tree - self.window = main.window - self.tree.signal_autoconnect(self) - - # all the package data is in this one VB - self.vb = self.tree.get_widget("packageVB") - - # the notebook - self.notebook = self.tree.get_widget("packageNotebook") - - # chechboxes - self.installedCheck = self.tree.get_widget("installedCheck") - self.maskedCheck = self.tree.get_widget("maskedCheck") - self.testingCheck = self.tree.get_widget("testingCheck") - self.maskedLabel = self.tree.get_widget("maskedLabel") - - # labels - generalVB = self.tree.get_widget("generalVB") - generalVB.modify_bg(gtk.STATE_NORMAL, get_color(self.main.cfg, "packagetable")) - - self.nameLabel = self.tree.get_widget("nameLabel") - self.descLabel = self.tree.get_widget("descLabel") - self.overlayLabel = self.tree.get_widget("overlayLabel") - self.overlayLL = self.tree.get_widget("overlayLabelLabel") - self.licenseLabel = self.tree.get_widget("licenseLabel") - self.linkBox = self.tree.get_widget("linkBox") - self.notInSysLabel = self.tree.get_widget("notInSysLabel") - self.missingLabel = self.tree.get_widget("missingLabel") - self.useFlagsLabel = self.tree.get_widget("useFlagsLabel") - self.useFlagsLL = self.tree.get_widget("useFlagsLabelLabel") - - # buttons - self.emergeBtn = self.tree.get_widget("pkgEmergeBtn") - self.unmergeBtn = self.tree.get_widget("pkgUnmergeBtn") - self.revertBtn = self.tree.get_widget("pkgRevertBtn") - - # useList - self.useList = self.tree.get_widget("useList") - self.build_use_list() - - # depList - self.depList = self.tree.get_widget("dependencyList") - self.build_dep_list() - - # views - self.ebuildView = self.tree.get_widget("ebuildScroll").get_child() - self.changelogView = self.tree.get_widget("changelogScroll").get_child() - self.filesView = self.tree.get_widget("filesScroll").get_child() - - # icons - self.icons = {} - self.icons["use"] = self.window.render_icon(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) - self.icons["installed"] = self.window.render_icon(gtk.STOCK_YES, gtk.ICON_SIZE_MENU) - self.icons["or"] = self.window.render_icon(gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU) - self.icons["block"] = self.window.render_icon(gtk.STOCK_NO, gtk.ICON_SIZE_MENU) - - def update (self, pkg, queue = None, doEmerge = True, instantChange = False, type = None): - """Updates the table to show the contents for the package. - - @param pkg: the selected package - @type pkg: Package - @param queue: emerge-queue (if None the emerge-buttons are disabled) - @type queue: EmergeQueue - @param doEmerge: if False, the emerge buttons are disabled - @type doEmerge: boolean - @param instantChange: if True the changed keywords are updated instantly - @type instantChange: boolean - @param type: the type of the queue this package is in; if None there is no queue :) - @type type: string""" - - self.pkg = pkg - self.queue = queue - self.doEmerge = doEmerge - self.instantChange = instantChange - self.type = type - - if not self.queue or not self.doEmerge: - self.emergeBtn.set_sensitive(False) - self.unmergeBtn.set_sensitive(False) - - # current status - self._update_table() - self.vb.show_all() - - def hide (self): - self.vb.hide_all() - - def set_labels (self): - pkg = self.pkg - - # name - self.nameLabel.set_markup("%s" % pkg.get_cpv()) - - # description - desc = pkg.get_package_settings("DESCRIPTION") or _("") - self.descLabel.set_label(desc) - - # overlay - if pkg.is_in_overlay(): - self.overlayLabel.set_label(pkg.get_overlay_path()) - self.overlayLabel.show() - self.overlayLL.show() - else: - self.overlayLabel.hide() - self.overlayLL.hide() - - # license - self.licenseLabel.set_label(pkg.get_package_settings("LICENSE")) - - # link - for c in self.linkBox.get_children(): - self.linkBox.remove(c) - - text = pkg.get_package_settings("HOMEPAGE") - texts = text.split(" ") - ftexts = [] - - for count, t in enumerate(texts): - if not t.startswith(("http", "ftp")): - if count == 0: - error(_("The first homepage part does not start with 'http' or 'ftp'.")) - ftexts.append(t) - continue - else: - info(_("Blank inside homepage.")) - ftexts[-1] += " %s" % t - else: - ftexts.append(t) - - for t in ftexts: - link = gtk.LinkButton(t) - link.set_alignment(0.0, 0.5) - link.set_border_width(0) - self.linkBox.add(link) - - # useflags - flaglist = list(itt.ifilterfalse(pkg.use_expanded, pkg.get_iuse_flags())) - flaglist.sort() - flags = ", ".join(flaglist) - - if flags: - self.useFlagsLL.show() - self.useFlagsLabel.show() - self.useFlagsLabel.set_label(flags) - else: - self.useFlagsLL.hide() - self.useFlagsLabel.hide() - - def fill_dep_list(self): - - store = self.depList.get_model() - - def add (tree, it): - - def get_icon (dep): - if dep.satisfied: - return self.icons["installed"] - elif dep.dep[0] == "!": - return self.icons["block"] - else: - return None - - # useflags - for use, usetree in tree.flags.iteritems(): - if use[0] == "!": - usestring = _("If '%s' is disabled") % use[1:] - else: - usestring = _("If '%s' is enabled") % use - useit = store.append(it, [self.icons["use"], usestring]) - add(usetree, useit) - - # ORs - ordeps = (dep for dep in tree.deps if isinstance(dep, dependency.OrDependency)) - - for ordep in ordeps: - orit = store.append(it, [self.icons["or"], _("One of the following")]) - - for dep in ordep.dep: - if isinstance(dep, dependency.AllOfDependency): # a list inside or - allit = store.append(orit, [None, _("All of the following")]) - for adep in dep.dep: - store.append(allit, [get_icon(adep), adep.dep]) - else: - store.append(orit, [get_icon(dep), dep.dep]) - - # normal - def sort_key (x): - split = system.split_cpv(x.dep) - - if split is None: # split_cpv returns None if this is only a CP; we assume there are only valid deps - return x.dep - else: - return "/".join(split[0:2]) - - ndeps = [dep for dep in tree.deps if not isinstance(dep, dependency.OrDependency)] - ndeps.sort(key = sort_key) - for dep in ndeps: - store.append(it, [get_icon(dep), dep.dep]) - - try: - deptree = self.pkg.get_dependencies() - except AssertionError: - w = _("Can't display dependencies: This package has an unsupported dependency string.") - error(w) - store.append(None, [None, w]) - else: - add(deptree, None) - - def fill_use_list(self): - - pkg = self.pkg - pkg_flags = pkg.get_iuse_flags() - pkg_flags.sort() - - actual_exp = None - actual_exp_it = None - - euse = pkg.get_actual_use_flags() - instuse = pkg.get_installed_use_flags() - - store = self.useList.get_model() - - for use in pkg_flags: - exp = pkg.use_expanded(use, suggest = actual_exp) - if exp is not None: - if exp != actual_exp: - actual_exp_it = store.append(None, [None, None, exp, "%s" % _("This is an expanded use flag and cannot be selected")]) - actual_exp = exp - else: - actual_exp_it = None - actual_exp = None - - enabled = use in euse - installed = use in instuse - store.append(actual_exp_it, [enabled, installed, use, system.get_use_desc(use, self.pkg.get_cp())]) - - def build_dep_list (self): - store = gtk.TreeStore(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 build_use_list (self): - """Builds the useList.""" - store = gtk.TreeStore(bool, bool, str, str) - self.useList.set_model(store) - - # build view - cell = gtk.CellRendererText() - iCell = gtk.CellRendererToggle() - iCell.set_property("activatable", False) - tCell = gtk.CellRendererToggle() - tCell.set_property("activatable", True) - tCell.connect("toggled", self.cb_use_flag_toggled, store) - self.useList.append_column(gtk.TreeViewColumn(_("Enabled"), tCell, active = 0)) - self.useList.append_column(gtk.TreeViewColumn(_("Installed"), iCell, active = 1)) - self.useList.append_column(gtk.TreeViewColumn(_("Flag"), cell, text = 2)) - self.useList.append_column(gtk.TreeViewColumn(_("Description"), cell, markup = 3)) - - self.useList.set_search_column(2) - self.useList.set_enable_tree_lines(True) - - def _update_keywords (self, emerge, update = False): - if emerge: - type = "install" if not self.type else self.type - try: - try: - self.queue.append(self.pkg.get_cpv(), type = type, update = update) - except PackageNotFoundException, e: - if unmask_dialog(e[0]) == gtk.RESPONSE_YES: - self.queue.append(self.pkg.get_cpv(), type = type, unmask = True, update = update) - except BlockedException, e: - blocked_dialog(e[0], e[1]) - else: - try: - self.queue.append(self.pkg.get_cpv(), type = "uninstall") - except PackageNotFoundException, e: - error(_("Package could not be found: %s"), e[0]) - #masked_dialog(e[0]) - - def _update_table (self, *args): - - pkg = self.pkg - - # set the views - for v in (self.ebuildView, self.changelogView, self.filesView): - v.update(pkg, force = self.notebook.get_nth_page(self.notebook.get_current_page()) == v.get_parent()) - - # set the labels - self.set_labels() - - # set use list - self.useList.get_model().clear() - self.useList.columns_autosize() - self.fill_use_list() - - # set dep list - self.depList.get_model().clear() - self.useList.columns_autosize() - self.fill_dep_list() - - # - # rebuild the buttons and checkboxes in all the different manners which are possible - # - if (not pkg.is_in_system()) or pkg.is_missing_keyword(): - if not pkg.is_in_system(): - self.missingLabel.hide() - self.notInSysLabel.show() - else: # missing keyword - self.missingLabel.show() - self.notInSysLabel.hide() -# - self.installedCheck.hide() - self.maskedCheck.hide() - self.maskedLabel.hide() - self.testingCheck.hide() - self.emergeBtn.set_sensitive(False) - else: # normal package - self.missingLabel.hide() - self.notInSysLabel.hide() - self.installedCheck.show() - self.maskedCheck.show() - self.maskedLabel.show() - self.testingCheck.show() - if self.doEmerge: - self.emergeBtn.set_sensitive(True) - self.installedCheck.set_active(pkg.is_installed()) - - reason = pkg.get_masking_reason() or " " - if pkg.is_masked(use_changed = False) and not pkg.is_masked(use_changed = True): - self.maskedCheck.set_label("(%s)" % _("Masked")) - self.maskedCheck.get_child().set_use_markup(True) - else: - self.maskedCheck.set_label(_("Masked")) - - if pkg.is_locally_masked(): - self.maskedCheck.set_label("%s" % _("Masked")) - self.maskedCheck.get_child().set_use_markup(True) - self.maskedCheck.set_active(True) - reason = _("Masked by user") - else: - self.maskedCheck.set_active(pkg.is_masked(use_changed = False)) - - if reason: - self.maskedLabel.set_label(reason) - - if pkg.is_testing(use_keywords = False) and not pkg.is_testing(use_keywords = True): - self.testingCheck.set_label("(%s)" % _("Testing")) - self.testingCheck.get_child().set_use_markup(True) - else: - self.testingCheck.set_label(_("Testing")) - - self.testingCheck.set_active(pkg.is_testing(use_keywords = False)) - - if self.doEmerge: - # set emerge-button-label - if not pkg.is_installed(): - self.unmergeBtn.set_sensitive(False) - else: - self.unmergeBtn.set_sensitive(True) - - self.vb.show_all() - return True - - def cb_button_pressed (self, b, event): - """Callback for pressed checkboxes. Just quits the event-loop - no redrawing.""" - if not isinstance(b, gtk.CellRendererToggle): - b.emit_stop_by_name("button-press-event") - return True - - def cb_package_revert_clicked (self, button): - """Callback for pressed revert-button.""" - self.pkg.remove_new_use_flags() - self.pkg.remove_new_masked() - self.pkg.remove_new_testing() - self._update_table() - if self.instantChange: - self._update_keywords(True, update = True) - return True - - def cb_package_emerge_clicked (self, button): - """Callback for pressed emerge-button. Adds the package to the EmergeQueue.""" - self._update_keywords(True) - self.main.sysNotebook.set_current_page(self.main.QUEUE_PAGE) - return True - - def cb_package_unmerge_clicked (self, button): - """Callback for pressed unmerge-button clicked. Adds the package to the UnmergeQueue.""" - self._update_keywords(False) - self.main.sysNotebook.set_current_page(self.main.QUEUE_PAGE) - return True - - def cb_testing_toggled (self, button): - """Callback for toggled testing-checkbox.""" - status = button.get_active() - - # end of recursion :) - if self.pkg.is_testing(use_keywords = False) == status: - return False - - # if the package is not testing - don't allow to set it as such - if not self.pkg.is_testing(use_keywords = False): - button.set_active(False) - return True - - # re-set to testing status - if not self.pkg.is_testing(use_keywords = True): - self.pkg.set_testing(False) - button.set_label(_("Testing")) - button.set_active(True) - else: # disable testing - self.pkg.set_testing(True) - button.set_label("(%s)" % _("Testing")) - button.get_child().set_use_markup(True) - button.set_active(True) - - if self.instantChange: - self._update_keywords(True, update = True) - - return True - - def cb_masked_toggled (self, button): - """Callback for toggled masking-checkbox.""" - status = button.get_active() - pkg = self.pkg - - if pkg.is_masked(use_changed = False) == status and not pkg.is_locally_masked(): - return False - - if pkg.is_locally_masked() and status: - return False - - if not pkg.is_masked(use_changed = True): - pkg.set_masked(True) - if pkg.is_locally_masked(): - button.set_label("%s" % _("Masked")) - button.get_child().set_use_markup(True) - self.maskedLabel.set_label(_("Masked by user")) - else: - button.set_label(_("Masked")) - - button.set_active(True) - else: - locally = pkg.is_locally_masked() - pkg.set_masked(False) - if pkg.is_masked(use_changed=False) and not locally: - button.set_label("(%s)" % _("Masked")) - button.get_child().set_use_markup(True) - button.set_active(True) - else: - button.set_label(_("Masked")) - self.maskedLabel.set_label("") - - if self.instantChange: - self._update_keywords(True, update = True) - - return True - - def cb_use_flag_toggled (self, cell, path, store): - """Callback for a toggled use-flag button.""" - flag = store[path][2] - pkg = self.pkg - - if pkg.use_expanded(flag): # ignore expanded flags - return False - - store[path][0] = not store[path][0] - prefix = "" - if not store[path][0]: - prefix = "-" - - pkg.set_use_flag(prefix+flag) - if self.instantChange: - self._update_keywords(True, update = True) - - return True + """A window with data about a specfic package.""" + + def __init__ (self, main): + """Build up window contents. + + @param main: the main window + @type main: MainWindow""" + + self.main = main + self.tree = main.tree + self.window = main.window + self.tree.signal_autoconnect(self) + + # all the package data is in this one VB + self.vb = self.tree.get_widget("packageVB") + + # the notebook + self.notebook = self.tree.get_widget("packageNotebook") + + # chechboxes + self.installedCheck = self.tree.get_widget("installedCheck") + self.maskedCheck = self.tree.get_widget("maskedCheck") + self.testingCheck = self.tree.get_widget("testingCheck") + self.maskedLabel = self.tree.get_widget("maskedLabel") + + # labels + generalVB = self.tree.get_widget("generalVB") + generalVB.modify_bg(gtk.STATE_NORMAL, get_color(self.main.cfg, "packagetable")) + + self.nameLabel = self.tree.get_widget("nameLabel") + self.descLabel = self.tree.get_widget("descLabel") + self.overlayLabel = self.tree.get_widget("overlayLabel") + self.overlayLL = self.tree.get_widget("overlayLabelLabel") + self.licenseLabel = self.tree.get_widget("licenseLabel") + self.linkBox = self.tree.get_widget("linkBox") + self.notInSysLabel = self.tree.get_widget("notInSysLabel") + self.missingLabel = self.tree.get_widget("missingLabel") + self.useFlagsLabel = self.tree.get_widget("useFlagsLabel") + self.useFlagsLL = self.tree.get_widget("useFlagsLabelLabel") + + # buttons + self.emergeBtn = self.tree.get_widget("pkgEmergeBtn") + self.unmergeBtn = self.tree.get_widget("pkgUnmergeBtn") + self.revertBtn = self.tree.get_widget("pkgRevertBtn") + + # useList + self.useList = self.tree.get_widget("useList") + self.build_use_list() + + # depList + self.depList = self.tree.get_widget("dependencyList") + self.build_dep_list() + + # views + self.ebuildView = self.tree.get_widget("ebuildScroll").get_child() + self.changelogView = self.tree.get_widget("changelogScroll").get_child() + self.filesView = self.tree.get_widget("filesScroll").get_child() + + # icons + self.icons = {} + self.icons["use"] = self.window.render_icon(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) + self.icons["installed"] = self.window.render_icon(gtk.STOCK_YES, gtk.ICON_SIZE_MENU) + self.icons["or"] = self.window.render_icon(gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU) + self.icons["block"] = self.window.render_icon(gtk.STOCK_NO, gtk.ICON_SIZE_MENU) + + def update (self, pkg, queue = None, doEmerge = True, instantChange = False, type = None): + """Updates the table to show the contents for the package. + + @param pkg: the selected package + @type pkg: Package + @param queue: emerge-queue (if None the emerge-buttons are disabled) + @type queue: EmergeQueue + @param doEmerge: if False, the emerge buttons are disabled + @type doEmerge: boolean + @param instantChange: if True the changed keywords are updated instantly + @type instantChange: boolean + @param type: the type of the queue this package is in; if None there is no queue :) + @type type: string""" + + self.pkg = pkg + self.queue = queue + self.doEmerge = doEmerge + self.instantChange = instantChange + self.type = type + + if not self.queue or not self.doEmerge: + self.emergeBtn.set_sensitive(False) + self.unmergeBtn.set_sensitive(False) + + # current status + self._update_table() + self.vb.show_all() + + def hide (self): + self.vb.hide_all() + + def set_labels (self): + pkg = self.pkg + + # name + self.nameLabel.set_markup("%s" % pkg.get_cpv()) + + # description + desc = pkg.get_package_settings("DESCRIPTION") or _("") + self.descLabel.set_label(desc) + + # overlay + if pkg.is_in_overlay(): + self.overlayLabel.set_label(pkg.get_overlay_path()) + self.overlayLabel.show() + self.overlayLL.show() + else: + self.overlayLabel.hide() + self.overlayLL.hide() + + # license + self.licenseLabel.set_label(pkg.get_package_settings("LICENSE")) + + # link + for c in self.linkBox.get_children(): + self.linkBox.remove(c) + + text = pkg.get_package_settings("HOMEPAGE") + texts = text.split(" ") + ftexts = [] + + for count, t in enumerate(texts): + if not t.startswith(("http", "ftp")): + if count == 0: + error(_("The first homepage part does not start with 'http' or 'ftp'.")) + ftexts.append(t) + continue + else: + info(_("Blank inside homepage.")) + ftexts[-1] += " %s" % t + else: + ftexts.append(t) + + for t in ftexts: + link = gtk.LinkButton(t) + link.set_alignment(0.0, 0.5) + link.set_border_width(0) + self.linkBox.add(link) + + # useflags + flaglist = list(itt.ifilterfalse(pkg.use_expanded, pkg.get_iuse_flags())) + flaglist.sort() + flags = ", ".join(flaglist) + + if flags: + self.useFlagsLL.show() + self.useFlagsLabel.show() + self.useFlagsLabel.set_label(flags) + else: + self.useFlagsLL.hide() + self.useFlagsLabel.hide() + + def fill_dep_list(self): + + store = self.depList.get_model() + + def add (tree, it): + + def get_icon (dep): + if dep.satisfied: + return self.icons["installed"] + elif dep.dep[0] == "!": + return self.icons["block"] + else: + return None + + # useflags + for use, usetree in tree.flags.iteritems(): + if use[0] == "!": + usestring = _("If '%s' is disabled") % use[1:] + else: + usestring = _("If '%s' is enabled") % use + useit = store.append(it, [self.icons["use"], usestring]) + add(usetree, useit) + + # ORs + ordeps = (dep for dep in tree.deps if isinstance(dep, dependency.OrDependency)) + + for ordep in ordeps: + orit = store.append(it, [self.icons["or"], _("One of the following")]) + + for dep in ordep.dep: + if isinstance(dep, dependency.AllOfDependency): # a list inside or + allit = store.append(orit, [None, _("All of the following")]) + for adep in dep.dep: + store.append(allit, [get_icon(adep), adep.dep]) + else: + store.append(orit, [get_icon(dep), dep.dep]) + + # normal + def sort_key (x): + split = system.split_cpv(x.dep) + + if split is None: # split_cpv returns None if this is only a CP; we assume there are only valid deps + return x.dep + else: + return "/".join(split[0:2]) + + ndeps = [dep for dep in tree.deps if not isinstance(dep, dependency.OrDependency)] + ndeps.sort(key = sort_key) + for dep in ndeps: + store.append(it, [get_icon(dep), dep.dep]) + + try: + deptree = self.pkg.get_dependencies() + except AssertionError: + w = _("Can't display dependencies: This package has an unsupported dependency string.") + error(w) + store.append(None, [None, w]) + else: + add(deptree, None) + + def fill_use_list(self): + + pkg = self.pkg + pkg_flags = pkg.get_iuse_flags() + pkg_flags.sort() + + actual_exp = None + actual_exp_it = None + + euse = pkg.get_actual_use_flags() + instuse = pkg.get_installed_use_flags() + + store = self.useList.get_model() + + for use in pkg_flags: + exp = pkg.use_expanded(use, suggest = actual_exp) + if exp is not None: + if exp != actual_exp: + actual_exp_it = store.append(None, [None, None, exp, "%s" % _("This is an expanded use flag and cannot be selected")]) + actual_exp = exp + else: + actual_exp_it = None + actual_exp = None + + enabled = use in euse + installed = use in instuse + store.append(actual_exp_it, [enabled, installed, use, system.get_use_desc(use, self.pkg.get_cp())]) + + def build_dep_list (self): + store = gtk.TreeStore(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 build_use_list (self): + """Builds the useList.""" + store = gtk.TreeStore(bool, bool, str, str) + self.useList.set_model(store) + + # build view + cell = gtk.CellRendererText() + iCell = gtk.CellRendererToggle() + iCell.set_property("activatable", False) + tCell = gtk.CellRendererToggle() + tCell.set_property("activatable", True) + tCell.connect("toggled", self.cb_use_flag_toggled, store) + self.useList.append_column(gtk.TreeViewColumn(_("Enabled"), tCell, active = 0)) + self.useList.append_column(gtk.TreeViewColumn(_("Installed"), iCell, active = 1)) + self.useList.append_column(gtk.TreeViewColumn(_("Flag"), cell, text = 2)) + self.useList.append_column(gtk.TreeViewColumn(_("Description"), cell, markup = 3)) + + self.useList.set_search_column(2) + self.useList.set_enable_tree_lines(True) + + def _update_keywords (self, emerge, update = False): + if emerge: + type = "install" if not self.type else self.type + try: + try: + self.queue.append(self.pkg.get_cpv(), type = type, update = update) + except PackageNotFoundException, e: + if unmask_dialog(e[0]) == gtk.RESPONSE_YES: + self.queue.append(self.pkg.get_cpv(), type = type, unmask = True, update = update) + except BlockedException, e: + blocked_dialog(e[0], e[1]) + else: + try: + self.queue.append(self.pkg.get_cpv(), type = "uninstall") + except PackageNotFoundException, e: + error(_("Package could not be found: %s"), e[0]) + #masked_dialog(e[0]) + + def _update_table (self, *args): + + pkg = self.pkg + + # set the views + for v in (self.ebuildView, self.changelogView, self.filesView): + v.update(pkg, force = self.notebook.get_nth_page(self.notebook.get_current_page()) == v.get_parent()) + + # set the labels + self.set_labels() + + # set use list + self.useList.get_model().clear() + self.useList.columns_autosize() + self.fill_use_list() + + # set dep list + self.depList.get_model().clear() + self.useList.columns_autosize() + self.fill_dep_list() + + # + # rebuild the buttons and checkboxes in all the different manners which are possible + # + if (not pkg.is_in_system()) or pkg.is_missing_keyword(): + if not pkg.is_in_system(): + self.missingLabel.hide() + self.notInSysLabel.show() + else: # missing keyword + self.missingLabel.show() + self.notInSysLabel.hide() +# + self.installedCheck.hide() + self.maskedCheck.hide() + self.maskedLabel.hide() + self.testingCheck.hide() + self.emergeBtn.set_sensitive(False) + else: # normal package + self.missingLabel.hide() + self.notInSysLabel.hide() + self.installedCheck.show() + self.maskedCheck.show() + self.maskedLabel.show() + self.testingCheck.show() + if self.doEmerge: + self.emergeBtn.set_sensitive(True) + self.installedCheck.set_active(pkg.is_installed()) + + reason = pkg.get_masking_reason() or " " + if pkg.is_masked(use_changed = False) and not pkg.is_masked(use_changed = True): + self.maskedCheck.set_label("(%s)" % _("Masked")) + self.maskedCheck.get_child().set_use_markup(True) + else: + self.maskedCheck.set_label(_("Masked")) + + if pkg.is_locally_masked(): + self.maskedCheck.set_label("%s" % _("Masked")) + self.maskedCheck.get_child().set_use_markup(True) + self.maskedCheck.set_active(True) + reason = _("Masked by user") + else: + self.maskedCheck.set_active(pkg.is_masked(use_changed = False)) + + if reason: + self.maskedLabel.set_label(reason) + + if pkg.is_testing(use_keywords = False) and not pkg.is_testing(use_keywords = True): + self.testingCheck.set_label("(%s)" % _("Testing")) + self.testingCheck.get_child().set_use_markup(True) + else: + self.testingCheck.set_label(_("Testing")) + + self.testingCheck.set_active(pkg.is_testing(use_keywords = False)) + + if self.doEmerge: + # set emerge-button-label + if not pkg.is_installed(): + self.unmergeBtn.set_sensitive(False) + else: + self.unmergeBtn.set_sensitive(True) + + self.vb.show_all() + return True + + def cb_button_pressed (self, b, event): + """Callback for pressed checkboxes. Just quits the event-loop - no redrawing.""" + if not isinstance(b, gtk.CellRendererToggle): + b.emit_stop_by_name("button-press-event") + return True + + def cb_package_revert_clicked (self, button): + """Callback for pressed revert-button.""" + self.pkg.remove_new_use_flags() + self.pkg.remove_new_masked() + self.pkg.remove_new_testing() + self._update_table() + if self.instantChange: + self._update_keywords(True, update = True) + return True + + def cb_package_emerge_clicked (self, button): + """Callback for pressed emerge-button. Adds the package to the EmergeQueue.""" + self._update_keywords(True) + self.main.sysNotebook.set_current_page(self.main.QUEUE_PAGE) + return True + + def cb_package_unmerge_clicked (self, button): + """Callback for pressed unmerge-button clicked. Adds the package to the UnmergeQueue.""" + self._update_keywords(False) + self.main.sysNotebook.set_current_page(self.main.QUEUE_PAGE) + return True + + def cb_testing_toggled (self, button): + """Callback for toggled testing-checkbox.""" + status = button.get_active() + + # end of recursion :) + if self.pkg.is_testing(use_keywords = False) == status: + return False + + # if the package is not testing - don't allow to set it as such + if not self.pkg.is_testing(use_keywords = False): + button.set_active(False) + return True + + # re-set to testing status + if not self.pkg.is_testing(use_keywords = True): + self.pkg.set_testing(False) + button.set_label(_("Testing")) + button.set_active(True) + else: # disable testing + self.pkg.set_testing(True) + button.set_label("(%s)" % _("Testing")) + button.get_child().set_use_markup(True) + button.set_active(True) + + if self.instantChange: + self._update_keywords(True, update = True) + + return True + + def cb_masked_toggled (self, button): + """Callback for toggled masking-checkbox.""" + status = button.get_active() + pkg = self.pkg + + if pkg.is_masked(use_changed = False) == status and not pkg.is_locally_masked(): + return False + + if pkg.is_locally_masked() and status: + return False + + if not pkg.is_masked(use_changed = True): + pkg.set_masked(True) + if pkg.is_locally_masked(): + button.set_label("%s" % _("Masked")) + button.get_child().set_use_markup(True) + self.maskedLabel.set_label(_("Masked by user")) + else: + button.set_label(_("Masked")) + + button.set_active(True) + else: + locally = pkg.is_locally_masked() + pkg.set_masked(False) + if pkg.is_masked(use_changed=False) and not locally: + button.set_label("(%s)" % _("Masked")) + button.get_child().set_use_markup(True) + button.set_active(True) + else: + button.set_label(_("Masked")) + self.maskedLabel.set_label("") + + if self.instantChange: + self._update_keywords(True, update = True) + + return True + + def cb_use_flag_toggled (self, cell, path, store): + """Callback for a toggled use-flag button.""" + flag = store[path][2] + pkg = self.pkg + + if pkg.use_expanded(flag): # ignore expanded flags + return False + + store[path][0] = not store[path][0] + prefix = "" + if not store[path][0]: + prefix = "-" + + pkg.set_use_flag(prefix+flag) + if self.instantChange: + self._update_keywords(True, update = True) + + return True class MainWindow (Window): - """ - Application main window. - """ - - # NOTEBOOK PAGE CONSTANTS - ( - QUEUE_PAGE, - CONSOLE_PAGE, - LOG_PAGE - ) = range(3) - - def __init__ (self, splash = None): - """ - Build up window. - - @param splash: the splash screen =) - @type splash: SplashScreen - """ - - if splash is None: - splash = lambda x: True - - # the title - self.main_title = "Portato (%s)" % VERSION - - # main window stuff - Window.__init__(self) - self.window.set_title(self.main_title) - self.window.set_geometry_hints (self.window, max_height = gtk.gdk.screen_height(), max_width = gtk.gdk.screen_width()) - - # booleans - self.doUpdate = False - self.showAll = True # show only installed or all packages? - self.__searchChanged = False - - # installed pixbuf - self.instPixbuf = self.window.render_icon(gtk.STOCK_YES, gtk.ICON_SIZE_MENU) - - # get the logging window as soon as possible - self.logView = LogView(self.tree.get_widget("logView")) - - # config - splash(_("Loading Config")) - try: - self.cfg = Config(CONFIG_LOCATION) - except IOError, e: - io_ex_dialog(e) - raise - - self.cfg.modify_external_configs() - self.set_uri_hook(self.cfg.get("browserCmd", section = "GUI")) - gtk.about_dialog_set_url_hook(lambda *args: True) # dummy - if not set link is not set as link; if link is clicked the normal uuri_hook is called too - thus do not call browser here - - # package db - splash(_("Creating Database")) - self.db = Database() - self.db.populate() - - # set plugins and plugin-menu - splash(_("Loading Plugins")) - - 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 itt.chain(*menus): - item = gtk.MenuItem(m.label) - item.connect("activate", m.call) - pluginMenu.append(item) - - splash(_("Building frontend")) - # set paned position - self.vpaned = self.tree.get_widget("vpaned") - self.vpaned.set_position(int(self.window.get_size()[1]/2)) - self.hpaned = self.tree.get_widget("hpaned") - self.hpaned.set_position(int(self.window.get_size()[0]/1.5)) - - # lists - self.selCatName = "" - self.selCP = "" - self.selCPV = "" - self.sortPkgListByName = True - self.catList = self.tree.get_widget("catList") - self.pkgList = self.tree.get_widget("pkgList") - self.versionList = self.tree.get_widget("versionList") - self.build_cat_list() - self.build_pkg_list() - self.build_version_list() - - # search entry - self.searchEntry = self.tree.get_widget("searchEntry") - - # queue list - self.queueOneshot = self.tree.get_widget("oneshotCB") - self.queueOneshotHandler = self.queueOneshot.connect("toggled", self.cb_oneshot_clicked) - self.queueList = self.tree.get_widget("queueList") - self.build_queue_list() - - # the terminal - self.console = GtkConsole() - self.termHB = self.tree.get_widget("termHB") - self.build_terminal() - - # notebooks - self.sysNotebook = self.tree.get_widget("systemNotebook") - self.pkgNotebook = self.tree.get_widget("packageNotebook") - self.set_notebook_tabpos(map(PreferenceWindow.tabpos.get, map(int, (self.cfg.get("packageTabPos", "GUI"), self.cfg.get("systemTabPos", "GUI"))))) - - # the different scrolls - ebuildScroll = self.tree.get_widget("ebuildScroll") - ebuildScroll.add(HighlightView(lambda p: p.get_ebuild_path(), ["gentoo", "sh"])) - - changelogScroll = self.tree.get_widget("changelogScroll") - changelogScroll.add(HighlightView(lambda p: os.path.join(p.get_package_path(), "ChangeLog"), ["changelog"])) - - def show_files (p): - try: - for f in p.get_files(): - yield " %s\n" % f - except IOError, e: - yield _("Error: %s") % e.strerror - - filesScroll = self.tree.get_widget("filesScroll") - filesScroll.add(InstalledOnlyView(show_files)) - - # table - self.packageTable = PackageTable(self) - - # popups - self.consolePopup = Popup("consolePopup", self, self.__file__) - self.trayPopup = Popup("systrayPopup", self) - - # pause menu items - self.emergePaused = False - self.pauseItems = {} - self.pauseItems["tray"] = self.trayPopup.tree.get_widget("pauseItemTray") - self.pauseItems["popup"] = self.consolePopup.tree.get_widget("pauseItemPopup") - self.pauseItems["menu"] = self.tree.get_widget("pauseItemMenu") - - for k,v in self.pauseItems.iteritems(): - self.pauseItems[k] = (v, v.connect_after("activate", self.cb_pause_emerge(k))) - - # systray - if self.cfg.get_boolean("showSystray", "GUI"): - self.tray = gtk.status_icon_new_from_file(APP_ICON) - self.tray.connect("activate", self.cb_systray_activated) - self.tray.connect("popup-menu", lambda icon, btn, time: self.trayPopup.popup(None, None, None, btn, time)) - else: - self.tray = None - - # set emerge queue - self.queueTree = GtkTree(self.queueList.get_model()) - self.queue = EmergeQueue(console = self.console, tree = self.queueTree, db = self.db, title_update = self.title_update, threadClass = GtkThread) - - # session - splash(_("Restoring Session")) - try: - try: - self.load_session() - except OldSessionException, e: - self.load_session(e) - except SessionException, e: - warning(str(e)) - - splash(_("Finishing startup")) - - self.window.show_all() - - def show_package (self, pkg = None, cpv = None, cp = None, version = None, **kwargs): - p = None - - if pkg: - p = pkg - elif cpv: - p = system.find_packages("="+cpv, masked = True)[0] - elif cp: - if version: - p = system.find_packages("=%s-%s" % (cp, version), masked = True)[0] - - else: - best = system.find_best_match(cp) - if best: - p = best - else: - p = system.find_packages(cp)[0] - - self.packageTable.update(p, **kwargs) - - def build_terminal (self): - """ - Builds the terminal. - """ - - self.console.set_scrollback_lines(int(self.cfg.get("scrollbacklines", "GUI"))) - self.console.set_scroll_on_output(True) - self.console.set_font_from_string(self.cfg.get("consolefont", "GUI")) - self.console.connect("button-press-event", self.cb_right_click) - self.termHB.pack_start(self.console, True, True) - - # add scrollbar - termScroll = gtk.VScrollbar(self.console.get_adjustment()) - self.termHB.pack_start(termScroll, False) - - def build_queue_list (self): - """ - Builds the queue list. - """ - - store = gtk.TreeStore(str,str,bool) - - self.queueList.set_model(store) - - cell = gtk.CellRendererText() - col = gtk.TreeViewColumn(_("Queue"), cell, markup = 0) - self.queueList.append_column(col) - - col = gtk.TreeViewColumn(_("Options"), cell, markup = 1) - self.queueList.append_column(col) - - self.queueList.get_selection().connect("changed", self.cb_queue_list_selection) - - def build_cat_list (self): - """ - Builds the category list. - """ - - store = gtk.TreeStore(str) - - self.fill_cat_store(store) - - self.catList.set_model(store) - cell = gtk.CellRendererText() - col = gtk.TreeViewColumn(_("Categories"), cell, text = 0) - self.catList.append_column(col) - - self.catList.get_selection().connect("changed", self.cb_cat_list_selection) - - def fill_cat_store (self, store = None): - """ - Fills the category store with data. - - @param store: the store to fill - @type store: gtk.ListStore - """ - - if store is None: - store = self.catList.get_model() - - store.clear() - - cats = self.db.get_categories(installed = not self.showAll) - - if not self.cfg.get_boolean("collapseCats", "GUI"): - for p in cats: - store.append(None, [p]) - else: - splitCats = defaultdict(list) - for c in cats: - try: - pre, post = c.split("-", 1) - except ValueError: # no "-" in cat name -- do not split - debug("Category '%s' can't be split up. Should be no harm.", c) - splitCats["not-split"].append(c) - else: - splitCats[pre].append(post) - - for sc in splitCats: - if sc == "not-split": - it = None # append not splitted stuff to root - else: - it = store.append(None, [sc]) - for cat in splitCats[sc]: - store.append(it, [cat]) - - # sort them alphabetically - store.set_sort_column_id(0, gtk.SORT_ASCENDING) - - def build_pkg_list (self, name = None): - """ - Builds the package list. - - @param name: name of the selected catetegory - @type name: string - """ - - store = gtk.ListStore(gtk.gdk.Pixbuf, str, str) - self.fill_pkg_store(store, name) - - # build view - self.pkgList.set_model(store) - - col = gtk.TreeViewColumn(_("Packages")) - col.set_clickable(True) - col.connect("clicked", self.cb_pkg_list_header_clicked) - - # adding the pixbuf - cell = gtk.CellRendererPixbuf() - col.pack_start(cell, False) - col.add_attribute(cell, "pixbuf", 0) - - # adding the package name - cell = gtk.CellRendererText() - col.pack_start(cell, True) - col.add_attribute(cell, "text", 1) - - self.pkgList.append_column(col) - - self.pkgList.get_selection().connect("changed", self.cb_pkg_list_selection) - - def fill_pkg_store (self, store = None, name = None): - """ - Fills a given ListStore with the packages in a category. - - @param store: the store to fill - @type store: gtk.ListStore - @param name: the name of the category - @type name: string - """ - - if store is None: - store = self.pkgList.get_model() - store.clear() - - if name: - for cat, pkg, is_inst in self.db.get_cat(name, self.sortPkgListByName): - if is_inst: - icon = self.instPixbuf - elif not self.showAll: - continue # ignore not installed packages - else: - icon = None - store.append([icon, pkg, cat]) - - def build_version_list (self): - store = gtk.ListStore(gtk.gdk.Pixbuf, str, str) - - # build view - self.versionList.set_model(store) - - col = gtk.TreeViewColumn(_("Versions")) - col.set_property("expand", True) - - self.slotcol = gtk.TreeViewColumn(_("Slot")) - self.slotcol.set_property("expand", True) - - # adding the pixbuf - cell = gtk.CellRendererPixbuf() - col.pack_start(cell, False) - col.add_attribute(cell, "pixbuf", 0) - - # adding the package name - cell = gtk.CellRendererText() - col.pack_start(cell, True) - col.add_attribute(cell, "text", 1) - - # adding the slot - cell = gtk.CellRendererText() - self.slotcol.pack_start(cell, True) - self.slotcol.add_attribute(cell, "text", 2) - - self.versionList.append_column(col) - self.versionList.append_column(self.slotcol) - - self.versionList.get_selection().connect("changed", self.cb_vers_list_selection) - - def fill_version_list (self, cp, version = None): - - store = self.versionList.get_model() - store.clear() - - # this is here for performance reasons - # to not query the package with info, we do not need - if self.cfg.get_boolean("showSlots", "GUI"): - def get_slot(pkg): - return pkg.get_package_settings("SLOT") - - self.slotcol.set_visible(True) - - else: - def get_slot(*args): - return "" - - self.slotcol.set_visible(False) - - packages = system.sort_package_list(system.find_packages(cp, masked=True)) - - # append versions - for vers, inst, slot in ((x.get_version(), x.is_installed(), get_slot(x)) for x in packages): - if inst: - icon = self.instPixbuf - else: - icon = None - - store.append([icon, vers, slot]) - - pos = ((0,)) # default - - # activate the first one - try: - best_version = "" - if version: - best_version = version - else: - best_version = system.find_best_match(packages[0].get_cp()).get_version() - for i, p in enumerate(packages): - if p.get_version() == best_version: - pos = (i,) - break - except AttributeError: # no package found - pass - - self.versionList.get_selection().select_path(pos) - self.versionList.scroll_to_cell(pos) - - def refresh_stores (self): - """ - Refreshes the category and package stores. - """ - self.fill_cat_store() - - if self.selCatName: - self.fill_pkg_store(name = self.selCatName) - else: # no selCatName -> so no category selected --> ignore - debug("No category selected --> should be no harm.") - - def load_session(self, sessionEx = None): - """ - Loads the session data. - """ - try: - self.session = Session("gtk_session.cfg") - except (OSError, IOError), e: - io_ex_dialog(e) - return - - oldVersion = SESSION_VERSION - allowedVersions = (0,1) - - if sessionEx and isinstance(sessionEx, SessionException): - if sessionEx.got in allowedVersions: - info(_("Translating session from version %d to %d.") % (sessionEx.got, sessionEx.expected)) - oldVersion = sessionEx.got - else: - warning(_("Cannot translate session from version %d to %d.") % (sessionEx.got, sessionEx.expected)) - raise sessionEx - - # - # the callbacks for the different session variables - # - - # QUEUE - def load_queue (merge, unmerge, oneshot): - def _load(q, **kwargs): - if q: - for i in q.split(","): - self.queue.append(i, **kwargs) - - _load(merge) - _load(unmerge, unmerge = True) - _load(oneshot, oneshot = True) - - def save_queue (): - if self.__save_queue: - return (",".join(self.queue.mergequeue), ",".join(self.queue.unmergequeue), ",".join(self.queue.oneshotmerge)) - else: - return ("", "", "") - - # PANED - def load_paned (*pos): - pos = map(int, pos) - [x.set_position(p) for x,p in zip((self.vpaned, self.hpaned), pos)] - - def save_paned (): - return [x.get_position() for x in (self.vpaned, self.hpaned)] - - # SELECTION - def load_selection (list, col): - def _load (name): - pos = "0" # default - - if name: - for cname, path in ((x[col], x.path) for x in list.get_model()): - if cname == name: - pos = path - break - - if self.cfg.get_boolean("collapseCats", "GUI") and \ - pos == "0" and isinstance(list.get_model(), gtk.TreeStore): # try the new split up - - try: - pre, post = name.split("-", 1) - except ValueError: # nothing to split - pass - else: - for row in list.get_model(): - if row[col] == pre: # found first part - pos = row.path - list.expand_row(pos, False) - for cname, path in ((x[col], x.path) for x in row.iterchildren()): - if cname == post: # found second - pos = ":".join(map(str,path)) - break - break - - debug("Selecting path '%s'.", pos) - list.get_selection().select_path(pos) - list.scroll_to_cell(pos) - - return _load - - def save_pkg_selection (): - store, iter = self.pkgList.get_selection().get_selected() - if iter: - return store.get_value(iter, 1) - else: - return "" - - def save_cat_selection (): - # try to find the correct category using the pkgList selection - # so we do not select ALL =) - # if no package has been selected - return selCatName - store, iter = self.pkgList.get_selection().get_selected() - if iter: - return store.get_value(iter, 2) - else: - return self.selCatName - - # PLUGIN - def load_plugin (p): - def _load(val): - if val: - p.status = int(val)*2 - - return _load - - def save_plugin (p): - def _save (): - if p.status == p.STAT_HARD_DISABLED: - return "" - - return int(p.status >= p.STAT_ENABLED) - - return _save - - # SESSION VERSION - def load_session_version (version): - if oldVersion != SESSION_VERSION: # we are trying to convert - return - - version = int(version) - - if version < SESSION_VERSION: - raise OldSessionException(version, SESSION_VERSION) - elif version > SESSION_VERSION: - raise NewSessionException(version, SESSION_VERSION) - - def _add (value): - if len(value) == 4: - self.session.add_handler(value[:3], default = value[3]) - else: - self.session.add_handler(value) - - # set the simple ones :) - map(_add,[ - ([("gtksessionversion", "session")], load_session_version, lambda: SESSION_VERSION), - ([("width", "window"), ("height", "window")], lambda w,h: self.window.resize(int(w), int(h)), self.window.get_size), - ([("vpanedpos", "window"), ("hpanedpos", "window")], load_paned, save_paned), - ([("catsel", "window")], load_selection(self.catList, 0), save_cat_selection, ["app-portage"]), - ([("pkgsel", "window")], load_selection(self.pkgList, 1), save_pkg_selection, ["portato"]) - #([("merge", "queue"), ("unmerge", "queue"), ("oneshot", "queue")], load_queue, save_queue), - ]) - - # set the plugins - queue = plugin.get_plugin_queue() - if queue: - for p in queue.get_plugins(): - self.session.add_handler(([(p.name.replace(" ","_"), "plugins")], load_plugin(p), save_plugin(p))) - - # now we have the handlers -> load - self.session.load() - - def jump_to (self, cp, version = None): - """ - Is called when we want to jump to a specific package. - - @param cp: the CP to jump to - @type cp: string - @param version: if not None jump to a specific version - @type version: string - """ - - cat, pkg = cp.split("/") - - for list, idx, what, expr in ((self.catList, 0, "categories", cat), (self.pkgList, 1, "packages", pkg)): - pathes = [row.path for row in list.get_model() if row[idx] == expr] - - if len(pathes) == 1: - list.get_selection().select_path(pathes[0]) - list.scroll_to_cell(pathes[0]) - else: - debug("Unexpected number of %s returned after search: %d", what, len(pathes)) - break - - self.show_package(cp = cp, version = version, queue = self.queue) - - def set_uri_hook (self, browser): - """ - Sets the browser command which is called when a URL is going to be opened. - - @param browser: the browser command - @type browser: string - """ - - browser = browser.split() - gtk.link_button_set_uri_hook(lambda btn, x: get_listener().send_cmd(browser+[btn.get_uri()])) - - def set_notebook_tabpos (self, tabposlist): - """ - Sets the positions of the tabs of the notebooks. - - @param tabposlist: the list of positions: first comes the one for package tabs; sndly for sys tabs - @type tabposlist: int[] - """ - self.pkgNotebook.set_tab_pos(tabposlist[0]) - self.sysNotebook.set_tab_pos(tabposlist[1]) - - def title_update (self, title): - """ - Updates the titles of the window and the systray. - Mainly used with emerge term titles. - - @param title: the title - @type title: string - """ - - def window_title_update (title): - """ - Updates the title of the main window. - """ - if title is None or not self.cfg.get_boolean("updateTitle", "GUI"): - self.window.set_title(self.main_title) - else: - title = title.strip() - if title[0] == '*': - self.window.set_title(self.main_title) - else: - space_idx = title.rfind(" ") - if space_idx != -1: - title = title[:space_idx] - - self.window.set_title(("Portato >>> %s" % title)) - - def __update(title): - if self.tray: - self.tray.set_tooltip(title) - - window_title_update(title) - if title is None or not self.cfg.get_boolean("updateConsole", "GUI"): - title = _("Console") - else: - title = ("%s (%s)") % (_("Console"), title) - - tlength = int(self.cfg.get("titlelength", "GUI")) - if (len(title) > tlength): title = "%s..." % title[:tlength-3] - self.sysNotebook.set_tab_label_text(self.termHB, title) - - return False - - # as this might get called from other threads use gobject.idle_add - gobject.idle_add(__update, title) - - def cb_cat_list_selection (self, selection): - """ - Callback for a category-list selection. - Updates the package list with the packages in the category. - """ - # get the selected category - store, it = selection.get_selected() - if it: - if not self.cfg.get_boolean("collapseCats", "GUI"): - self.selCatName = store.get_value(it, 0) - else: - parent = store.iter_parent(it) - if parent is None: - if store.iter_has_child(it): # this is a split up selector -> do nothing - return True - else: - self.selCatName = store.get_value(it, 0) # this is a non-split up top - else: - self.selCatName = ("%s-%s" % (store.get_value(parent, 0), store.get_value(it, 0))) - - self.fill_pkg_store(name = self.selCatName) - return True - - def cb_pkg_list_selection (self, selection): - """ - Callback for a package-list selection. - Updates the version list. - """ - store, it = selection.get_selected() - if it: - self.selCP = "%s/%s" % (store.get_value(it, 2), store.get_value(it, 1)) - self.fill_version_list(self.selCP) - return True - - def cb_pkg_list_header_clicked(self, col): - self.sortPkgListByName = not self.sortPkgListByName - self.fill_pkg_store(name = self.selCatName) - return True - - def cb_vers_list_selection (self, selection): - """ - Callback for a package-list selection. - Updates the version list. - """ - store, it = selection.get_selected() - if it: - self.selCPV = "%s-%s" % (self.selCP, store.get_value(it, 1)) - self.show_package(cpv = self.selCPV, queue = self.queue) - - return True - - def cb_queue_list_selection (self, selection): - - def set_val (val): - self.queueOneshot.handler_block(self.queueOneshotHandler) - self.queueOneshot.set_active(val) - self.queueOneshot.handler_unblock(self.queueOneshotHandler) - - store, it = selection.get_selected() - if it: - parent = self.queueTree.parent_iter(it) - if self.queueTree.is_in_emerge(it) and parent and not self.queueTree.iter_has_parent(parent): - package = store.get_value(it, 0) - self.queueOneshot.set_sensitive(True) - set_val(package in self.queue.oneshotmerge) - return True - - self.queueOneshot.set_sensitive(False) - set_val(False) - return True - - def cb_queue_row_activated (self, view, path, *args): - """Callback for an activated row in the emergeQueue. Opens a package window.""" - store = self.queueTree - if len(path) > 1: - iterator = store.get_original().get_iter(path) - if store.iter_has_parent(iterator): - package = store.get_value(iterator, store.get_cpv_column()) - - if store.is_in_emerge(iterator): - type = "install" - elif store.is_in_unmerge(iterator): - type = "uninstall" - elif store.is_in_update(iterator): - type = "update" - - self.show_package(cpv = package, queue = self.queue, instantChange = True, doEmerge = False, type = type) - return True - - def cb_queue_tooltip_queried (self, view, x, y, is_keyboard, tooltip): - store = self.queueList.get_model() - path = self.queueList.get_path_at_pos(x,y) - - if path is None: - return False - - it = store.get_iter(path[0]) - - if store.iter_parent(it) is None: - return False # do not show tooltips for the root entries - - pkg = system.new_package(store.get_value(it, 0)) - - enabled = [] - disabled = [] - expanded = set() - - pkg_flags = pkg.get_iuse_flags() - pkg_flags.sort() - if not pkg_flags: # no flags - stop here - return None - - actual = set(pkg.get_actual_use_flags()) - - if pkg.is_installed(): - installed = set(pkg.get_iuse_flags()).intersection(pkg.get_installed_use_flags()) - else: - inst = system.find_packages(pkg.get_slot_cp(), system.SET_INSTALLED) - if inst: - installed = set(inst[0].get_iuse_flags()).intersection(inst[0].get_installed_use_flags()) - else: - installed = set() - - diff = actual.symmetric_difference(installed) - - for use in pkg_flags: - exp = pkg.use_expanded(use) - if exp: - expanded.add(exp) - - else: - useStr = use - if installed and use in diff: - useStr += " %" - if use in actual: - enabled.append(useStr) - else: - disabled.append(useStr) - - string = "" - - if enabled: - string = "+%s" % ("\n+".join(enabled),) - if len(disabled) > 0: - string = string + "\n" - - if disabled: - string = string+"- %s" % ("\n- ".join(disabled),) - - if expanded: - string = string+"\n\n"+"\n".join(expanded) - - tooltip.set_markup(string) - return string != "" - - def cb_execute_clicked (self, action): - """Execute the current queue.""" - - if len(flags.newUseFlags) > 0: - if not self.session.get_boolean("useflags", "dialogs"): - self.session.set("useflags", changed_flags_dialog(_("use flags"))[1], "dialogs") - try: - flags.write_use_flags() - except IOError, e: - io_ex_dialog(e) - return True - - if len(flags.new_masked)>0 or len(flags.new_unmasked)>0 or len(flags.newTesting)>0: - debug("new masked: %s",flags.new_masked) - debug("new unmasked: %s", flags.new_unmasked) - debug("new testing: %s", flags.newTesting) - if not self.session.get_boolean("keywords", "dialogs"): - self.session.set("keywords", changed_flags_dialog(_("masking keywords"))[1], "dialogs") - try: - flags.write_masked() - flags.write_testing() - except IOError, e: - io_ex_dialog(e) - return True - else: - system.reload_settings() - - model, iter = self.queueList.get_selection().get_selected() - - if iter is None: - if model.iter_n_children(None) == 1: # only one queue there - take this as being selected - iter = model.get_iter_root() - else: - return False - - self.sysNotebook.set_current_page(self.CONSOLE_PAGE) - - # test which type of queue we have here - if self.queueTree.is_in_emerge(iter): - self.queue.emerge(force = True) - elif self.queueTree.is_in_unmerge(iter): - self.queue.unmerge(force = True) - else: - self.queue.update_world(sets = self.updateSets, force=True, newuse = self.cfg.get_boolean("newuse"), deep = self.cfg.get_boolean("deep")) - - return True - - def cb_update_clicked (self, action): - def __update(): - - def cb_idle_append (updating): - try: - try: - for pkg, old_pkg in updating: - self.queue.append(pkg.get_cpv(), type = "update", unmask = False) - except PackageNotFoundException, e: - if unmask_dialog(e[0]) == gtk.RESPONSE_YES: - for pkg, old_pkg in updating: - self.queue.append(pkg.get_cpv(), type = "update", unmask = True) - - except BlockedException, e: - blocked_dialog(e[0], e[1]) - self.queue.remove_children(self.queueTree.get_update_it()) - - return False - - watch = gtk.gdk.Cursor(gtk.gdk.WATCH) - self.window.window.set_cursor(watch) - try: - if system.has_set_support(): - confsets = [x.strip() for x in self.cfg.get("updatesets").split(",")] - self.updateSets = [s for s in confsets if s in system.get_sets()] - updating = system.update_world(sets = self.updateSets, newuse = self.cfg.get_boolean("newuse"), deep = self.cfg.get_boolean("deep")) - else: - updating = system.update_world(newuse = self.cfg.get_boolean("newuse"), deep = self.cfg.get_boolean("deep")) - self.updateSets = ("world",) - - debug("updating list: %s --> length: %s", [(x.get_cpv(), y.get_cpv()) for x,y in updating], len(updating)) - gobject.idle_add(cb_idle_append, updating) - finally: - self.window.window.set_cursor(None) - - GtkThread(name="Update-Thread", target=__update).start() - - return True - - def cb_remove_clicked (self, button): - """Removes a selected item in the (un)emerge-queue if possible.""" - model, iter = self.queueList.get_selection().get_selected() - - if iter: - parent = model.iter_parent(iter) - - if self.queueTree.is_in_update(iter) and parent: - if remove_updates_dialog() == gtk.RESPONSE_YES: - self.queue.remove_with_children(self.queueTree.get_update_it()) - - elif not parent: # top-level - if model.iter_n_children(iter) > 0: # and has children which can be removed :) - if remove_queue_dialog() == gtk.RESPONSE_YES : - self.queue.remove_with_children(iter) - else: - self.queue.remove(iter) - - elif model.iter_parent(parent): # this is in the 3rd level => dependency - remove_deps_dialog() - else: - self.queue.remove_with_children(iter) - - if model.iter_n_children(parent) == 0: # no more children left - remove queue too - self.queue.remove(parent) - - return True - return False - - def cb_sync_clicked (self, action): - self.sysNotebook.set_current_page(self.CONSOLE_PAGE) - cmd = self.cfg.get("syncCommand") - - if cmd != "emerge --sync": - cmd = cmd.split() - self.queue.sync(cmd) - else: - self.queue.sync() - - def cb_save_flags_clicked (self, action): - try: - flags.write_use_flags() - flags.write_testing() - flags.write_masked() - except IOError, e: - io_ex_dialog(e) - - @Window.watch_cursor - def cb_reload_clicked (self, action): - """Reloads the portage settings and the database.""" - system.reload_settings() - self.db.reload() - - @Window.watch_cursor - def cb_search_clicked (self, entry): - """Do a search.""" - text = entry.get_text() - if text != "": - if "/" not in text: - text = "/.*"+text # only look for package names - - packages = system.find_packages(text, with_version = False) - - if packages == []: - nothing_found_dialog() - else: - if len(packages) == 1: - self.jump_to(packages[0]) - else: - SearchWindow(self.window, packages, self.jump_to) - - return True - - def cb_search_changed (self, *args): - """ - Called when the user enters something in the search field. - Updates the packages according to the search expression. - """ - if not self.__searchChanged and self.cfg.get_boolean("searchOnType", section="GUI"): - self.__searchChanged = True - - def __update(): - self.__searchChanged = False - txt = self.searchEntry.get_text() - - if txt or self.db.restrict: - self.db.restrict = txt - - self.refresh_stores() - self.catList.get_selection().select_path("0") # XXX make this smarter - - return False # not again ;) - - gobject.timeout_add(100, __update) - - def cb_delete_search_clicked (self, *args): - self.searchEntry.set_text("") - return True - - def cb_preferences_clicked (self, *args): - """ - User wants to open preferences. - """ - PreferenceWindow(self.window, self.cfg, self.console.set_font_from_string, self.set_uri_hook, self.set_notebook_tabpos, self.fill_cat_store) - return True - - def cb_about_clicked (self, *args): - """ - User wants to open about dialog. - """ - AboutWindow(self.window) - return True - - def cb_plugins_clicked (self, *args): - """ - User wants to open plugin dialog. - """ - queue = plugin.get_plugin_queue() - if queue is None: - plugins = [] - else: - plugins = list(queue.get_plugins()) - - PluginWindow(self.window, plugins, self.queue) - return True - - def cb_show_updates_clicked (self, *args): - """ - Show the list of updateble packages. - """ - - def __update(): - def cb_idle_show(packages): - """ - Callback opening the menu when the calculation is finished. - - @returns: False to signal that it is finished - """ - UpdateWindow(self.window, packages, self.queue, self.jump_to) - return False - - watch = gtk.gdk.Cursor(gtk.gdk.WATCH) - self.window.window.set_cursor(watch) - - packages = [] - try: - packages.extend(system.get_updated_packages()) - finally: - self.window.window.set_cursor(None) - - gobject.idle_add(cb_idle_show, packages) - - GtkThread(name="Show Updates Thread", target = __update).start() - return True - - def cb_show_installed_toggled (self, *args): - """ - Toggle the "show only installed" option. - """ - self.showAll = not self.showAll - self.refresh_stores() - - def cb_right_click (self, object, event): - """ - Called when the user right clicks somewhere. - Used to display a menu. - - This method should handle ALL such menus. - - @param object: the object/widget where the click is done - @type object: gtk.Widget - @param event: the event triggered - @type event: gtk.gdk.Event - """ - - if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3: # 3 == right click - x = int(event.x) - y = int(event.y) - time = event.time - - if object == self.console: - self.consolePopup.popup(None, None, None, event.button, time) - else: - return False - else: - return False - - def cb_oneshot_clicked (self, *args): - """ - Mark a package as oneshot. - """ - sel = self.queueList.get_selection() - store, it = sel.get_selected() - if it: - if self.queueTree.is_in_emerge(it) and self.queueTree.iter_has_parent(it): - package = store.get_value(it, 0) - set = (package not in self.queue.oneshotmerge) - - self.queue.append(package, update = True, oneshot = set, forceUpdate = True) - - def cb_pause_emerge (self, curr): - """ - This method returns a callback for a "pause emerge" toggle button. - It is needed as there are different toggle buttons of this type and if one is clicked, - the others should be marked too. - - @param curr: The button to return the callback for. - @type curr: gtk.ToggleButton - """ - def pause (cb): - """ - The actual callback. - - Mark all other buttons too. - - @param cb: The button which got toggled. - @type cb: gtk.ToggleButton - """ - - # pause or continue - self.emergePaused = cb.get_active() - if not self.emergePaused: - self.queue.continue_emerge() - #self.tray.set_from_file(APP_ICON) - else: - self.queue.stop_emerge() - #self.tray.set_from_file(os.path.join(ICON_DIR, "pausing.png")) - - # block the handlers of the other buttons - # so that calling "set_active" does not call this callback recursivly - for v in self.pauseItems.itervalues(): - v[0].handler_block(v[1]) - - # mark the others - for k, v in self.pauseItems.iteritems(): - if k != curr: - v[0].set_active(self.emergePaused) - - # unblock - for v in self.pauseItems.itervalues(): - v[0].handler_unblock(v[1]) - - return False - return pause - - def cb_kill_clicked (self, *args): - """ - Kill emerge. - """ - self.queue.kill_emerge() - if self.emergePaused: # unmark the "pause emerge" buttons - self.pauseItems["menu"][0].set_active(False) # calling one button is enough (see: cb_pause_emerge) - - def cb_copy_clicked (self, *args): - """ - Copy marked text in the terminal to clipboard. - """ - self.console.copy_clipboard() - - def cb_delete (self, *args): - """ - Called when the user wants to quit the application. - - Asks the user for confirmation if there is something in the queue. - Also saves session data. - """ - - self.__save_queue = False - - if not self.queue.is_empty(): - ret = queue_not_empty_dialog() - if ret == gtk.RESPONSE_CANCEL: - return True - else: # there is sth in queue AND the user still wants to close -> kill emerge - self.__save_queue = (ret == gtk.RESPONSE_YES) - self.queue.kill_emerge() - - # write session - self.session.save() - - return False - - def cb_minimized (self, window, event): - """ - User wants to minimize the window. - If it is possible to minimize to tray, it is done. - """ - - if self.tray and self.cfg.get_boolean("hideOnMinimize", "GUI"): - if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED: - if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED: - self.window.hide() - return True - - return False - - def cb_systray_activated (self, *args): - """ - Systray was activated. Show or hide the window. - """ - if self.window.iconify_initially: - self.window.deiconify() - self.window.show() - self.window.window.show() - else: - self.window.iconify() - - def cb_close (self, *args): - """ - "Close" menu entry called. - Emulate normal quitting. - """ - if not self.cb_delete(): # do the checks - self.window.destroy() - - def cb_destroy (self, *args): - """ - Calls main_quit(). - """ - gtk.main_quit() - - def main (self): - """ - Main. - """ - gobject.threads_init() - # now subthreads can run normally, but are not allowed to touch the GUI. If threads should change sth there - use gobject.idle_add(). - # for more informations on threading and gtk: http://www.async.com.br/faq/pygtk/index.py?req=show&file=faq20.006.htp - plugin.hook("main")(gtk.main)() + """ + Application main window. + """ + + # NOTEBOOK PAGE CONSTANTS + ( + QUEUE_PAGE, + CONSOLE_PAGE, + LOG_PAGE + ) = range(3) + + def __init__ (self, splash = None): + """ + Build up window. + + @param splash: the splash screen =) + @type splash: SplashScreen + """ + + if splash is None: + splash = lambda x: True + + # the title + self.main_title = "Portato (%s)" % VERSION + + # main window stuff + Window.__init__(self) + self.window.set_title(self.main_title) + self.window.set_geometry_hints (self.window, max_height = gtk.gdk.screen_height(), max_width = gtk.gdk.screen_width()) + + # booleans + self.doUpdate = False + self.showAll = True # show only installed or all packages? + self.__searchChanged = False + + # installed pixbuf + self.instPixbuf = self.window.render_icon(gtk.STOCK_YES, gtk.ICON_SIZE_MENU) + + # get the logging window as soon as possible + self.logView = LogView(self.tree.get_widget("logView")) + + # config + splash(_("Loading Config")) + try: + self.cfg = Config(CONFIG_LOCATION) + except IOError, e: + io_ex_dialog(e) + raise + + self.cfg.modify_external_configs() + self.set_uri_hook(self.cfg.get("browserCmd", section = "GUI")) + gtk.about_dialog_set_url_hook(lambda *args: True) # dummy - if not set link is not set as link; if link is clicked the normal uuri_hook is called too - thus do not call browser here + + # package db + splash(_("Creating Database")) + self.db = Database() + self.db.populate() + + # set plugins and plugin-menu + splash(_("Loading Plugins")) + + 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 itt.chain(*menus): + item = gtk.MenuItem(m.label) + item.connect("activate", m.call) + pluginMenu.append(item) + + splash(_("Building frontend")) + # set paned position + self.vpaned = self.tree.get_widget("vpaned") + self.vpaned.set_position(int(self.window.get_size()[1]/2)) + self.hpaned = self.tree.get_widget("hpaned") + self.hpaned.set_position(int(self.window.get_size()[0]/1.5)) + + # lists + self.selCatName = "" + self.selCP = "" + self.selCPV = "" + self.sortPkgListByName = True + self.catList = self.tree.get_widget("catList") + self.pkgList = self.tree.get_widget("pkgList") + self.versionList = self.tree.get_widget("versionList") + self.build_cat_list() + self.build_pkg_list() + self.build_version_list() + + # search entry + self.searchEntry = self.tree.get_widget("searchEntry") + + # queue list + self.queueOneshot = self.tree.get_widget("oneshotCB") + self.queueOneshotHandler = self.queueOneshot.connect("toggled", self.cb_oneshot_clicked) + self.queueList = self.tree.get_widget("queueList") + self.build_queue_list() + + # the terminal + self.console = GtkConsole() + self.termHB = self.tree.get_widget("termHB") + self.build_terminal() + + # notebooks + self.sysNotebook = self.tree.get_widget("systemNotebook") + self.pkgNotebook = self.tree.get_widget("packageNotebook") + self.set_notebook_tabpos(map(PreferenceWindow.tabpos.get, map(int, (self.cfg.get("packageTabPos", "GUI"), self.cfg.get("systemTabPos", "GUI"))))) + + # the different scrolls + ebuildScroll = self.tree.get_widget("ebuildScroll") + ebuildScroll.add(HighlightView(lambda p: p.get_ebuild_path(), ["gentoo", "sh"])) + + changelogScroll = self.tree.get_widget("changelogScroll") + changelogScroll.add(HighlightView(lambda p: os.path.join(p.get_package_path(), "ChangeLog"), ["changelog"])) + + def show_files (p): + try: + for f in p.get_files(): + yield " %s\n" % f + except IOError, e: + yield _("Error: %s") % e.strerror + + filesScroll = self.tree.get_widget("filesScroll") + filesScroll.add(InstalledOnlyView(show_files)) + + # table + self.packageTable = PackageTable(self) + + # popups + self.consolePopup = Popup("consolePopup", self, self.__file__) + self.trayPopup = Popup("systrayPopup", self) + + # pause menu items + self.emergePaused = False + self.pauseItems = {} + self.pauseItems["tray"] = self.trayPopup.tree.get_widget("pauseItemTray") + self.pauseItems["popup"] = self.consolePopup.tree.get_widget("pauseItemPopup") + self.pauseItems["menu"] = self.tree.get_widget("pauseItemMenu") + + for k,v in self.pauseItems.iteritems(): + self.pauseItems[k] = (v, v.connect_after("activate", self.cb_pause_emerge(k))) + + # systray + if self.cfg.get_boolean("showSystray", "GUI"): + self.tray = gtk.status_icon_new_from_file(APP_ICON) + self.tray.connect("activate", self.cb_systray_activated) + self.tray.connect("popup-menu", lambda icon, btn, time: self.trayPopup.popup(None, None, None, btn, time)) + else: + self.tray = None + + # set emerge queue + self.queueTree = GtkTree(self.queueList.get_model()) + self.queue = EmergeQueue(console = self.console, tree = self.queueTree, db = self.db, title_update = self.title_update, threadClass = GtkThread) + + # session + splash(_("Restoring Session")) + try: + try: + self.load_session() + except OldSessionException, e: + self.load_session(e) + except SessionException, e: + warning(str(e)) + + splash(_("Finishing startup")) + + self.window.show_all() + + def show_package (self, pkg = None, cpv = None, cp = None, version = None, **kwargs): + p = None + + if pkg: + p = pkg + elif cpv: + p = system.find_packages("="+cpv, masked = True)[0] + elif cp: + if version: + p = system.find_packages("=%s-%s" % (cp, version), masked = True)[0] + + else: + best = system.find_best_match(cp) + if best: + p = best + else: + p = system.find_packages(cp)[0] + + self.packageTable.update(p, **kwargs) + + def build_terminal (self): + """ + Builds the terminal. + """ + + self.console.set_scrollback_lines(int(self.cfg.get("scrollbacklines", "GUI"))) + self.console.set_scroll_on_output(True) + self.console.set_font_from_string(self.cfg.get("consolefont", "GUI")) + self.console.connect("button-press-event", self.cb_right_click) + self.termHB.pack_start(self.console, True, True) + + # add scrollbar + termScroll = gtk.VScrollbar(self.console.get_adjustment()) + self.termHB.pack_start(termScroll, False) + + def build_queue_list (self): + """ + Builds the queue list. + """ + + store = gtk.TreeStore(str,str,bool) + + self.queueList.set_model(store) + + cell = gtk.CellRendererText() + col = gtk.TreeViewColumn(_("Queue"), cell, markup = 0) + self.queueList.append_column(col) + + col = gtk.TreeViewColumn(_("Options"), cell, markup = 1) + self.queueList.append_column(col) + + self.queueList.get_selection().connect("changed", self.cb_queue_list_selection) + + def build_cat_list (self): + """ + Builds the category list. + """ + + store = gtk.TreeStore(str) + + self.fill_cat_store(store) + + self.catList.set_model(store) + cell = gtk.CellRendererText() + col = gtk.TreeViewColumn(_("Categories"), cell, text = 0) + self.catList.append_column(col) + + self.catList.get_selection().connect("changed", self.cb_cat_list_selection) + + def fill_cat_store (self, store = None): + """ + Fills the category store with data. + + @param store: the store to fill + @type store: gtk.ListStore + """ + + if store is None: + store = self.catList.get_model() + + store.clear() + + cats = self.db.get_categories(installed = not self.showAll) + + if not self.cfg.get_boolean("collapseCats", "GUI"): + for p in cats: + store.append(None, [p]) + else: + splitCats = defaultdict(list) + for c in cats: + try: + pre, post = c.split("-", 1) + except ValueError: # no "-" in cat name -- do not split + debug("Category '%s' can't be split up. Should be no harm.", c) + splitCats["not-split"].append(c) + else: + splitCats[pre].append(post) + + for sc in splitCats: + if sc == "not-split": + it = None # append not splitted stuff to root + else: + it = store.append(None, [sc]) + for cat in splitCats[sc]: + store.append(it, [cat]) + + # sort them alphabetically + store.set_sort_column_id(0, gtk.SORT_ASCENDING) + + def build_pkg_list (self, name = None): + """ + Builds the package list. + + @param name: name of the selected catetegory + @type name: string + """ + + store = gtk.ListStore(gtk.gdk.Pixbuf, str, str) + self.fill_pkg_store(store, name) + + # build view + self.pkgList.set_model(store) + + col = gtk.TreeViewColumn(_("Packages")) + col.set_clickable(True) + col.connect("clicked", self.cb_pkg_list_header_clicked) + + # adding the pixbuf + cell = gtk.CellRendererPixbuf() + col.pack_start(cell, False) + col.add_attribute(cell, "pixbuf", 0) + + # adding the package name + cell = gtk.CellRendererText() + col.pack_start(cell, True) + col.add_attribute(cell, "text", 1) + + self.pkgList.append_column(col) + + self.pkgList.get_selection().connect("changed", self.cb_pkg_list_selection) + + def fill_pkg_store (self, store = None, name = None): + """ + Fills a given ListStore with the packages in a category. + + @param store: the store to fill + @type store: gtk.ListStore + @param name: the name of the category + @type name: string + """ + + if store is None: + store = self.pkgList.get_model() + store.clear() + + if name: + for cat, pkg, is_inst in self.db.get_cat(name, self.sortPkgListByName): + if is_inst: + icon = self.instPixbuf + elif not self.showAll: + continue # ignore not installed packages + else: + icon = None + store.append([icon, pkg, cat]) + + def build_version_list (self): + store = gtk.ListStore(gtk.gdk.Pixbuf, str, str) + + # build view + self.versionList.set_model(store) + + col = gtk.TreeViewColumn(_("Versions")) + col.set_property("expand", True) + + self.slotcol = gtk.TreeViewColumn(_("Slot")) + self.slotcol.set_property("expand", True) + + # adding the pixbuf + cell = gtk.CellRendererPixbuf() + col.pack_start(cell, False) + col.add_attribute(cell, "pixbuf", 0) + + # adding the package name + cell = gtk.CellRendererText() + col.pack_start(cell, True) + col.add_attribute(cell, "text", 1) + + # adding the slot + cell = gtk.CellRendererText() + self.slotcol.pack_start(cell, True) + self.slotcol.add_attribute(cell, "text", 2) + + self.versionList.append_column(col) + self.versionList.append_column(self.slotcol) + + self.versionList.get_selection().connect("changed", self.cb_vers_list_selection) + + def fill_version_list (self, cp, version = None): + + store = self.versionList.get_model() + store.clear() + + # this is here for performance reasons + # to not query the package with info, we do not need + if self.cfg.get_boolean("showSlots", "GUI"): + def get_slot(pkg): + return pkg.get_package_settings("SLOT") + + self.slotcol.set_visible(True) + + else: + def get_slot(*args): + return "" + + self.slotcol.set_visible(False) + + packages = system.sort_package_list(system.find_packages(cp, masked=True)) + + # append versions + for vers, inst, slot in ((x.get_version(), x.is_installed(), get_slot(x)) for x in packages): + if inst: + icon = self.instPixbuf + else: + icon = None + + store.append([icon, vers, slot]) + + pos = ((0,)) # default + + # activate the first one + try: + best_version = "" + if version: + best_version = version + else: + best_version = system.find_best_match(packages[0].get_cp()).get_version() + for i, p in enumerate(packages): + if p.get_version() == best_version: + pos = (i,) + break + except AttributeError: # no package found + pass + + self.versionList.get_selection().select_path(pos) + self.versionList.scroll_to_cell(pos) + + def refresh_stores (self): + """ + Refreshes the category and package stores. + """ + self.fill_cat_store() + + if self.selCatName: + self.fill_pkg_store(name = self.selCatName) + else: # no selCatName -> so no category selected --> ignore + debug("No category selected --> should be no harm.") + + def load_session(self, sessionEx = None): + """ + Loads the session data. + """ + try: + self.session = Session("gtk_session.cfg") + except (OSError, IOError), e: + io_ex_dialog(e) + return + + oldVersion = SESSION_VERSION + allowedVersions = (0,1) + + if sessionEx and isinstance(sessionEx, SessionException): + if sessionEx.got in allowedVersions: + info(_("Translating session from version %d to %d.") % (sessionEx.got, sessionEx.expected)) + oldVersion = sessionEx.got + else: + warning(_("Cannot translate session from version %d to %d.") % (sessionEx.got, sessionEx.expected)) + raise sessionEx + + # + # the callbacks for the different session variables + # + + # QUEUE + def load_queue (merge, unmerge, oneshot): + def _load(q, **kwargs): + if q: + for i in q.split(","): + self.queue.append(i, **kwargs) + + _load(merge) + _load(unmerge, unmerge = True) + _load(oneshot, oneshot = True) + + def save_queue (): + if self.__save_queue: + return (",".join(self.queue.mergequeue), ",".join(self.queue.unmergequeue), ",".join(self.queue.oneshotmerge)) + else: + return ("", "", "") + + # PANED + def load_paned (*pos): + pos = map(int, pos) + [x.set_position(p) for x,p in zip((self.vpaned, self.hpaned), pos)] + + def save_paned (): + return [x.get_position() for x in (self.vpaned, self.hpaned)] + + # SELECTION + def load_selection (list, col): + def _load (name): + pos = "0" # default + + if name: + for cname, path in ((x[col], x.path) for x in list.get_model()): + if cname == name: + pos = path + break + + if self.cfg.get_boolean("collapseCats", "GUI") and \ + pos == "0" and isinstance(list.get_model(), gtk.TreeStore): # try the new split up + + try: + pre, post = name.split("-", 1) + except ValueError: # nothing to split + pass + else: + for row in list.get_model(): + if row[col] == pre: # found first part + pos = row.path + list.expand_row(pos, False) + for cname, path in ((x[col], x.path) for x in row.iterchildren()): + if cname == post: # found second + pos = ":".join(map(str,path)) + break + break + + debug("Selecting path '%s'.", pos) + list.get_selection().select_path(pos) + list.scroll_to_cell(pos) + + return _load + + def save_pkg_selection (): + store, iter = self.pkgList.get_selection().get_selected() + if iter: + return store.get_value(iter, 1) + else: + return "" + + def save_cat_selection (): + # try to find the correct category using the pkgList selection + # so we do not select ALL =) + # if no package has been selected - return selCatName + store, iter = self.pkgList.get_selection().get_selected() + if iter: + return store.get_value(iter, 2) + else: + return self.selCatName + + # PLUGIN + def load_plugin (p): + def _load(val): + if val: + p.status = int(val)*2 + + return _load + + def save_plugin (p): + def _save (): + if p.status == p.STAT_HARD_DISABLED: + return "" + + return int(p.status >= p.STAT_ENABLED) + + return _save + + # SESSION VERSION + def load_session_version (version): + if oldVersion != SESSION_VERSION: # we are trying to convert + return + + version = int(version) + + if version < SESSION_VERSION: + raise OldSessionException(version, SESSION_VERSION) + elif version > SESSION_VERSION: + raise NewSessionException(version, SESSION_VERSION) + + def _add (value): + if len(value) == 4: + self.session.add_handler(value[:3], default = value[3]) + else: + self.session.add_handler(value) + + # set the simple ones :) + map(_add,[ + ([("gtksessionversion", "session")], load_session_version, lambda: SESSION_VERSION), + ([("width", "window"), ("height", "window")], lambda w,h: self.window.resize(int(w), int(h)), self.window.get_size), + ([("vpanedpos", "window"), ("hpanedpos", "window")], load_paned, save_paned), + ([("catsel", "window")], load_selection(self.catList, 0), save_cat_selection, ["app-portage"]), + ([("pkgsel", "window")], load_selection(self.pkgList, 1), save_pkg_selection, ["portato"]) + #([("merge", "queue"), ("unmerge", "queue"), ("oneshot", "queue")], load_queue, save_queue), + ]) + + # set the plugins + queue = plugin.get_plugin_queue() + if queue: + for p in queue.get_plugins(): + self.session.add_handler(([(p.name.replace(" ","_"), "plugins")], load_plugin(p), save_plugin(p))) + + # now we have the handlers -> load + self.session.load() + + def jump_to (self, cp, version = None): + """ + Is called when we want to jump to a specific package. + + @param cp: the CP to jump to + @type cp: string + @param version: if not None jump to a specific version + @type version: string + """ + + cat, pkg = cp.split("/") + + for list, idx, what, expr in ((self.catList, 0, "categories", cat), (self.pkgList, 1, "packages", pkg)): + pathes = [row.path for row in list.get_model() if row[idx] == expr] + + if len(pathes) == 1: + list.get_selection().select_path(pathes[0]) + list.scroll_to_cell(pathes[0]) + else: + debug("Unexpected number of %s returned after search: %d", what, len(pathes)) + break + + self.show_package(cp = cp, version = version, queue = self.queue) + + def set_uri_hook (self, browser): + """ + Sets the browser command which is called when a URL is going to be opened. + + @param browser: the browser command + @type browser: string + """ + + browser = browser.split() + gtk.link_button_set_uri_hook(lambda btn, x: get_listener().send_cmd(browser+[btn.get_uri()])) + + def set_notebook_tabpos (self, tabposlist): + """ + Sets the positions of the tabs of the notebooks. + + @param tabposlist: the list of positions: first comes the one for package tabs; sndly for sys tabs + @type tabposlist: int[] + """ + self.pkgNotebook.set_tab_pos(tabposlist[0]) + self.sysNotebook.set_tab_pos(tabposlist[1]) + + def title_update (self, title): + """ + Updates the titles of the window and the systray. + Mainly used with emerge term titles. + + @param title: the title + @type title: string + """ + + def window_title_update (title): + """ + Updates the title of the main window. + """ + if title is None or not self.cfg.get_boolean("updateTitle", "GUI"): + self.window.set_title(self.main_title) + else: + title = title.strip() + if title[0] == '*': + self.window.set_title(self.main_title) + else: + space_idx = title.rfind(" ") + if space_idx != -1: + title = title[:space_idx] + + self.window.set_title(("Portato >>> %s" % title)) + + def __update(title): + if self.tray: + self.tray.set_tooltip(title) + + window_title_update(title) + if title is None or not self.cfg.get_boolean("updateConsole", "GUI"): + title = _("Console") + else: + title = ("%s (%s)") % (_("Console"), title) + + tlength = int(self.cfg.get("titlelength", "GUI")) + if (len(title) > tlength): title = "%s..." % title[:tlength-3] + self.sysNotebook.set_tab_label_text(self.termHB, title) + + return False + + # as this might get called from other threads use gobject.idle_add + gobject.idle_add(__update, title) + + def cb_cat_list_selection (self, selection): + """ + Callback for a category-list selection. + Updates the package list with the packages in the category. + """ + # get the selected category + store, it = selection.get_selected() + if it: + if not self.cfg.get_boolean("collapseCats", "GUI"): + self.selCatName = store.get_value(it, 0) + else: + parent = store.iter_parent(it) + if parent is None: + if store.iter_has_child(it): # this is a split up selector -> do nothing + return True + else: + self.selCatName = store.get_value(it, 0) # this is a non-split up top + else: + self.selCatName = ("%s-%s" % (store.get_value(parent, 0), store.get_value(it, 0))) + + self.fill_pkg_store(name = self.selCatName) + return True + + def cb_pkg_list_selection (self, selection): + """ + Callback for a package-list selection. + Updates the version list. + """ + store, it = selection.get_selected() + if it: + self.selCP = "%s/%s" % (store.get_value(it, 2), store.get_value(it, 1)) + self.fill_version_list(self.selCP) + return True + + def cb_pkg_list_header_clicked(self, col): + self.sortPkgListByName = not self.sortPkgListByName + self.fill_pkg_store(name = self.selCatName) + return True + + def cb_vers_list_selection (self, selection): + """ + Callback for a package-list selection. + Updates the version list. + """ + store, it = selection.get_selected() + if it: + self.selCPV = "%s-%s" % (self.selCP, store.get_value(it, 1)) + self.show_package(cpv = self.selCPV, queue = self.queue) + + return True + + def cb_queue_list_selection (self, selection): + + def set_val (val): + self.queueOneshot.handler_block(self.queueOneshotHandler) + self.queueOneshot.set_active(val) + self.queueOneshot.handler_unblock(self.queueOneshotHandler) + + store, it = selection.get_selected() + if it: + parent = self.queueTree.parent_iter(it) + if self.queueTree.is_in_emerge(it) and parent and not self.queueTree.iter_has_parent(parent): + package = store.get_value(it, 0) + self.queueOneshot.set_sensitive(True) + set_val(package in self.queue.oneshotmerge) + return True + + self.queueOneshot.set_sensitive(False) + set_val(False) + return True + + def cb_queue_row_activated (self, view, path, *args): + """Callback for an activated row in the emergeQueue. Opens a package window.""" + store = self.queueTree + if len(path) > 1: + iterator = store.get_original().get_iter(path) + if store.iter_has_parent(iterator): + package = store.get_value(iterator, store.get_cpv_column()) + + if store.is_in_emerge(iterator): + type = "install" + elif store.is_in_unmerge(iterator): + type = "uninstall" + elif store.is_in_update(iterator): + type = "update" + + self.show_package(cpv = package, queue = self.queue, instantChange = True, doEmerge = False, type = type) + return True + + def cb_queue_tooltip_queried (self, view, x, y, is_keyboard, tooltip): + store = self.queueList.get_model() + path = self.queueList.get_path_at_pos(x,y) + + if path is None: + return False + + it = store.get_iter(path[0]) + + if store.iter_parent(it) is None: + return False # do not show tooltips for the root entries + + pkg = system.new_package(store.get_value(it, 0)) + + enabled = [] + disabled = [] + expanded = set() + + pkg_flags = pkg.get_iuse_flags() + pkg_flags.sort() + if not pkg_flags: # no flags - stop here + return None + + actual = set(pkg.get_actual_use_flags()) + + if pkg.is_installed(): + installed = set(pkg.get_iuse_flags()).intersection(pkg.get_installed_use_flags()) + else: + inst = system.find_packages(pkg.get_slot_cp(), system.SET_INSTALLED) + if inst: + installed = set(inst[0].get_iuse_flags()).intersection(inst[0].get_installed_use_flags()) + else: + installed = set() + + diff = actual.symmetric_difference(installed) + + for use in pkg_flags: + exp = pkg.use_expanded(use) + if exp: + expanded.add(exp) + + else: + useStr = use + if installed and use in diff: + useStr += " %" + if use in actual: + enabled.append(useStr) + else: + disabled.append(useStr) + + string = "" + + if enabled: + string = "+%s" % ("\n+".join(enabled),) + if len(disabled) > 0: + string = string + "\n" + + if disabled: + string = string+"- %s" % ("\n- ".join(disabled),) + + if expanded: + string = string+"\n\n"+"\n".join(expanded) + + tooltip.set_markup(string) + return string != "" + + def cb_execute_clicked (self, action): + """Execute the current queue.""" + + if len(flags.newUseFlags) > 0: + if not self.session.get_boolean("useflags", "dialogs"): + self.session.set("useflags", changed_flags_dialog(_("use flags"))[1], "dialogs") + try: + flags.write_use_flags() + except IOError, e: + io_ex_dialog(e) + return True + + if len(flags.new_masked)>0 or len(flags.new_unmasked)>0 or len(flags.newTesting)>0: + debug("new masked: %s",flags.new_masked) + debug("new unmasked: %s", flags.new_unmasked) + debug("new testing: %s", flags.newTesting) + if not self.session.get_boolean("keywords", "dialogs"): + self.session.set("keywords", changed_flags_dialog(_("masking keywords"))[1], "dialogs") + try: + flags.write_masked() + flags.write_testing() + except IOError, e: + io_ex_dialog(e) + return True + else: + system.reload_settings() + + model, iter = self.queueList.get_selection().get_selected() + + if iter is None: + if model.iter_n_children(None) == 1: # only one queue there - take this as being selected + iter = model.get_iter_root() + else: + return False + + self.sysNotebook.set_current_page(self.CONSOLE_PAGE) + + # test which type of queue we have here + if self.queueTree.is_in_emerge(iter): + self.queue.emerge(force = True) + elif self.queueTree.is_in_unmerge(iter): + self.queue.unmerge(force = True) + else: + self.queue.update_world(sets = self.updateSets, force=True, newuse = self.cfg.get_boolean("newuse"), deep = self.cfg.get_boolean("deep")) + + return True + + def cb_update_clicked (self, action): + def __update(): + + def cb_idle_append (updating): + try: + try: + for pkg, old_pkg in updating: + self.queue.append(pkg.get_cpv(), type = "update", unmask = False) + except PackageNotFoundException, e: + if unmask_dialog(e[0]) == gtk.RESPONSE_YES: + for pkg, old_pkg in updating: + self.queue.append(pkg.get_cpv(), type = "update", unmask = True) + + except BlockedException, e: + blocked_dialog(e[0], e[1]) + self.queue.remove_children(self.queueTree.get_update_it()) + + return False + + watch = gtk.gdk.Cursor(gtk.gdk.WATCH) + self.window.window.set_cursor(watch) + try: + if system.has_set_support(): + confsets = [x.strip() for x in self.cfg.get("updatesets").split(",")] + self.updateSets = [s for s in confsets if s in system.get_sets()] + updating = system.update_world(sets = self.updateSets, newuse = self.cfg.get_boolean("newuse"), deep = self.cfg.get_boolean("deep")) + else: + updating = system.update_world(newuse = self.cfg.get_boolean("newuse"), deep = self.cfg.get_boolean("deep")) + self.updateSets = ("world",) + + debug("updating list: %s --> length: %s", [(x.get_cpv(), y.get_cpv()) for x,y in updating], len(updating)) + gobject.idle_add(cb_idle_append, updating) + finally: + self.window.window.set_cursor(None) + + GtkThread(name="Update-Thread", target=__update).start() + + return True + + def cb_remove_clicked (self, button): + """Removes a selected item in the (un)emerge-queue if possible.""" + model, iter = self.queueList.get_selection().get_selected() + + if iter: + parent = model.iter_parent(iter) + + if self.queueTree.is_in_update(iter) and parent: + if remove_updates_dialog() == gtk.RESPONSE_YES: + self.queue.remove_with_children(self.queueTree.get_update_it()) + + elif not parent: # top-level + if model.iter_n_children(iter) > 0: # and has children which can be removed :) + if remove_queue_dialog() == gtk.RESPONSE_YES : + self.queue.remove_with_children(iter) + else: + self.queue.remove(iter) + + elif model.iter_parent(parent): # this is in the 3rd level => dependency + remove_deps_dialog() + else: + self.queue.remove_with_children(iter) + + if model.iter_n_children(parent) == 0: # no more children left - remove queue too + self.queue.remove(parent) + + return True + return False + + def cb_sync_clicked (self, action): + self.sysNotebook.set_current_page(self.CONSOLE_PAGE) + cmd = self.cfg.get("syncCommand") + + if cmd != "emerge --sync": + cmd = cmd.split() + self.queue.sync(cmd) + else: + self.queue.sync() + + def cb_save_flags_clicked (self, action): + try: + flags.write_use_flags() + flags.write_testing() + flags.write_masked() + except IOError, e: + io_ex_dialog(e) + + @Window.watch_cursor + def cb_reload_clicked (self, action): + """Reloads the portage settings and the database.""" + system.reload_settings() + self.db.reload() + + @Window.watch_cursor + def cb_search_clicked (self, entry): + """Do a search.""" + text = entry.get_text() + if text != "": + if "/" not in text: + text = "/.*"+text # only look for package names + + packages = system.find_packages(text, with_version = False) + + if packages == []: + nothing_found_dialog() + else: + if len(packages) == 1: + self.jump_to(packages[0]) + else: + SearchWindow(self.window, packages, self.jump_to) + + return True + + def cb_search_changed (self, *args): + """ + Called when the user enters something in the search field. + Updates the packages according to the search expression. + """ + if not self.__searchChanged and self.cfg.get_boolean("searchOnType", section="GUI"): + self.__searchChanged = True + + def __update(): + self.__searchChanged = False + txt = self.searchEntry.get_text() + + if txt or self.db.restrict: + self.db.restrict = txt + + self.refresh_stores() + self.catList.get_selection().select_path("0") # XXX make this smarter + + return False # not again ;) + + gobject.timeout_add(100, __update) + + def cb_delete_search_clicked (self, *args): + self.searchEntry.set_text("") + return True + + def cb_preferences_clicked (self, *args): + """ + User wants to open preferences. + """ + PreferenceWindow(self.window, self.cfg, self.console.set_font_from_string, self.set_uri_hook, self.set_notebook_tabpos, self.fill_cat_store) + return True + + def cb_about_clicked (self, *args): + """ + User wants to open about dialog. + """ + AboutWindow(self.window) + return True + + def cb_plugins_clicked (self, *args): + """ + User wants to open plugin dialog. + """ + queue = plugin.get_plugin_queue() + if queue is None: + plugins = [] + else: + plugins = list(queue.get_plugins()) + + PluginWindow(self.window, plugins, self.queue) + return True + + def cb_show_updates_clicked (self, *args): + """ + Show the list of updateble packages. + """ + + def __update(): + def cb_idle_show(packages): + """ + Callback opening the menu when the calculation is finished. + + @returns: False to signal that it is finished + """ + UpdateWindow(self.window, packages, self.queue, self.jump_to) + return False + + watch = gtk.gdk.Cursor(gtk.gdk.WATCH) + self.window.window.set_cursor(watch) + + packages = [] + try: + packages.extend(system.get_updated_packages()) + finally: + self.window.window.set_cursor(None) + + gobject.idle_add(cb_idle_show, packages) + + GtkThread(name="Show Updates Thread", target = __update).start() + return True + + def cb_show_installed_toggled (self, *args): + """ + Toggle the "show only installed" option. + """ + self.showAll = not self.showAll + self.refresh_stores() + + def cb_right_click (self, object, event): + """ + Called when the user right clicks somewhere. + Used to display a menu. + + This method should handle ALL such menus. + + @param object: the object/widget where the click is done + @type object: gtk.Widget + @param event: the event triggered + @type event: gtk.gdk.Event + """ + + if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3: # 3 == right click + x = int(event.x) + y = int(event.y) + time = event.time + + if object == self.console: + self.consolePopup.popup(None, None, None, event.button, time) + else: + return False + else: + return False + + def cb_oneshot_clicked (self, *args): + """ + Mark a package as oneshot. + """ + sel = self.queueList.get_selection() + store, it = sel.get_selected() + if it: + if self.queueTree.is_in_emerge(it) and self.queueTree.iter_has_parent(it): + package = store.get_value(it, 0) + set = (package not in self.queue.oneshotmerge) + + self.queue.append(package, update = True, oneshot = set, forceUpdate = True) + + def cb_pause_emerge (self, curr): + """ + This method returns a callback for a "pause emerge" toggle button. + It is needed as there are different toggle buttons of this type and if one is clicked, + the others should be marked too. + + @param curr: The button to return the callback for. + @type curr: gtk.ToggleButton + """ + def pause (cb): + """ + The actual callback. + + Mark all other buttons too. + + @param cb: The button which got toggled. + @type cb: gtk.ToggleButton + """ + + # pause or continue + self.emergePaused = cb.get_active() + if not self.emergePaused: + self.queue.continue_emerge() + #self.tray.set_from_file(APP_ICON) + else: + self.queue.stop_emerge() + #self.tray.set_from_file(os.path.join(ICON_DIR, "pausing.png")) + + # block the handlers of the other buttons + # so that calling "set_active" does not call this callback recursivly + for v in self.pauseItems.itervalues(): + v[0].handler_block(v[1]) + + # mark the others + for k, v in self.pauseItems.iteritems(): + if k != curr: + v[0].set_active(self.emergePaused) + + # unblock + for v in self.pauseItems.itervalues(): + v[0].handler_unblock(v[1]) + + return False + return pause + + def cb_kill_clicked (self, *args): + """ + Kill emerge. + """ + self.queue.kill_emerge() + if self.emergePaused: # unmark the "pause emerge" buttons + self.pauseItems["menu"][0].set_active(False) # calling one button is enough (see: cb_pause_emerge) + + def cb_copy_clicked (self, *args): + """ + Copy marked text in the terminal to clipboard. + """ + self.console.copy_clipboard() + + def cb_delete (self, *args): + """ + Called when the user wants to quit the application. + + Asks the user for confirmation if there is something in the queue. + Also saves session data. + """ + + self.__save_queue = False + + if not self.queue.is_empty(): + ret = queue_not_empty_dialog() + if ret == gtk.RESPONSE_CANCEL: + return True + else: # there is sth in queue AND the user still wants to close -> kill emerge + self.__save_queue = (ret == gtk.RESPONSE_YES) + self.queue.kill_emerge() + + # write session + self.session.save() + + return False + + def cb_minimized (self, window, event): + """ + User wants to minimize the window. + If it is possible to minimize to tray, it is done. + """ + + if self.tray and self.cfg.get_boolean("hideOnMinimize", "GUI"): + if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED: + if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED: + self.window.hide() + return True + + return False + + def cb_systray_activated (self, *args): + """ + Systray was activated. Show or hide the window. + """ + if self.window.iconify_initially: + self.window.deiconify() + self.window.show() + self.window.window.show() + else: + self.window.iconify() + + def cb_close (self, *args): + """ + "Close" menu entry called. + Emulate normal quitting. + """ + if not self.cb_delete(): # do the checks + self.window.destroy() + + def cb_destroy (self, *args): + """ + Calls main_quit(). + """ + gtk.main_quit() + + def main (self): + """ + Main. + """ + gobject.threads_init() + # now subthreads can run normally, but are not allowed to touch the GUI. If threads should change sth there - use gobject.idle_add(). + # for more informations on threading and gtk: http://www.async.com.br/faq/pygtk/index.py?req=show&file=faq20.006.htp + plugin.hook("main")(gtk.main)() diff --git a/portato/gui/windows/plugin.py b/portato/gui/windows/plugin.py index a0694be..eccf302 100644 --- a/portato/gui/windows/plugin.py +++ b/portato/gui/windows/plugin.py @@ -21,161 +21,161 @@ from ...backend.exceptions import PackageNotFoundException, BlockedException from ...helper import debug class PluginWindow (AbstractDialog): - - statsStore = gtk.ListStore(str) - - for s in (_("Disabled"), _("Temporarily enabled"), _("Enabled"), _("Temporarily disabled")): - statsStore.append([s]) - - def __init__ (self, parent, plugins, queue = None): - """Constructor. - - @param parent: the parent window - @type parent: gtk.Window""" - - AbstractDialog.__init__(self, parent) - self.plugins = plugins - self.queue = queue - self.changedPlugins = {} - self.inst = [] - self.ninst = [] - - 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) - - self.view = self.tree.get_widget("pluginList") - self.store = gtk.ListStore(str) - - self.view.set_model(self.store) - - cell = gtk.CellRendererText() - col = gtk.TreeViewColumn("Plugin", cell, markup = 0) - self.view.append_column(col) - - for p in plugins: - self.store.append(["%s" % p.name]) - - self.view.get_selection().connect("changed", self.cb_list_selection) - - self.window.show_all() - - 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]) - - def cb_state_toggled (self, rb): - - plugin = self.get_actual() - - if plugin: - state = self.buttons.index(rb) - - 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(): - plugin.status = val - - 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 + + statsStore = gtk.ListStore(str) + + for s in (_("Disabled"), _("Temporarily enabled"), _("Enabled"), _("Temporarily disabled")): + statsStore.append([s]) + + def __init__ (self, parent, plugins, queue = None): + """Constructor. + + @param parent: the parent window + @type parent: gtk.Window""" + + AbstractDialog.__init__(self, parent) + self.plugins = plugins + self.queue = queue + self.changedPlugins = {} + self.inst = [] + self.ninst = [] + + 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) + + self.view = self.tree.get_widget("pluginList") + self.store = gtk.ListStore(str) + + self.view.set_model(self.store) + + cell = gtk.CellRendererText() + col = gtk.TreeViewColumn("Plugin", cell, markup = 0) + self.view.append_column(col) + + for p in plugins: + self.store.append(["%s" % p.name]) + + self.view.get_selection().connect("changed", self.cb_list_selection) + + self.window.show_all() + + 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]) + + def cb_state_toggled (self, rb): + + plugin = self.get_actual() + + if plugin: + state = self.buttons.index(rb) + + 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(): + plugin.status = val + + 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/gui/windows/preference.py b/portato/gui/windows/preference.py index dba28b1..94d7ade 100644 --- a/portato/gui/windows/preference.py +++ b/portato/gui/windows/preference.py @@ -22,197 +22,197 @@ from ..utils import get_color from ...helper import debug class PreferenceWindow (AbstractDialog): - """Window displaying some preferences.""" - - # all checkboxes in the window - # widget name -> option name - checkboxes = { - "collapseCatCheck" : ("collapseCats", "GUI"), - "consoleUpdateCheck" : ("updateConsole", "GUI"), - "debugCheck" : "debug", - "deepCheck" : "deep", - "newUseCheck" : "newuse", - "maskPerVersionCheck" : "maskPerVersion", - "minimizeCheck" : ("hideOnMinimize", "GUI"), - "searchOnTypeCheck" : ("searchOnType", "GUI"), - "showSlotsCheck" : ("showSlots", "GUI"), - "systrayCheck" : ("showSystray", "GUI"), - "testPerVersionCheck" : "keywordPerVersion", - "titleUpdateCheck" : ("updateTitle", "GUI"), - "usePerVersionCheck" : "usePerVersion" - } - - # all edits in the window - # widget name -> option name - edits = { - "maskFileEdit" : "maskFile", - "testFileEdit" : "keywordFile", - "useFileEdit" : "useFile", - "syncCommandEdit" : "syncCommand", - "browserEdit" : ("browserCmd", "GUI") - } - - # the mappings for the tabpos combos - tabpos = { - 1 : gtk.POS_TOP, - 2 : gtk.POS_BOTTOM, - 3 : gtk.POS_LEFT, - 4 : gtk.POS_RIGHT - } - - def __init__ (self, parent, cfg, console_fn, linkbtn_fn, tabpos_fn, catmodel_fn): - """Constructor. - - @param parent: parent window - @type parent: gtk.Window - @param cfg: configuration object - @type cfg: gui_helper.Config - @param console_fn: function to call to set the console font - @type console_fn: function(string) - @param linkbtn_fn: function to call to set the linkbutton behavior - @type linkbtn_fn: function(string) - @param tabpos_fn: function to call to set the tabposition of the notebooks - @type tabpos_fn: function(gtk.ComboBox,int) - @param catmodel_fn: function to call to set the model of the cat list (collapsed/not collapsed) - @type catmodel_fn: function()""" - - AbstractDialog.__init__(self, parent) - - # our config - self.cfg = cfg - - # the setter functions - self.console_fn = console_fn - self.linkbtn_fn = linkbtn_fn - self.tabpos_fn = tabpos_fn - self.catmodel_fn = catmodel_fn - - # set the bg-color of the hint - hintEB = self.tree.get_widget("hintEB") - hintEB.modify_bg(gtk.STATE_NORMAL, get_color(self.cfg, "prefhint")) - - # the checkboxes - for box, val in self.checkboxes.iteritems(): - if isinstance(val, tuple): - self.tree.get_widget(box).\ - set_active(self.cfg.get_boolean(val[0], section = val[1])) - else: - self.tree.get_widget(box).\ - set_active(self.cfg.get_boolean(val)) - - # the edits - for edit, val in self.edits.iteritems(): - if isinstance(val,tuple): - self.tree.get_widget(edit).\ - set_text(self.cfg.get(val[0], section = val[1])) - else: - self.tree.get_widget(edit).\ - set_text(self.cfg.get(val)) - - # the set list - self.setList = self.tree.get_widget("setList") - if system.has_set_support(): - self.fill_setlist() - self.tree.get_widget("setFrame").show() - - # the console font button - self.consoleFontBtn = self.tree.get_widget("consoleFontBtn") - self.consoleFontBtn.set_font_name(self.cfg.get("consolefont", section = "GUI")) - - # the console title length spin button - self.titleLengthSpinBtn = self.tree.get_widget("titleLengthSpinBtn") - self.titleLengthSpinBtn.set_value(int(self.cfg.get("titlelength", section = "GUI"))) - - # the comboboxes - self.systemTabCombo = self.tree.get_widget("systemTabCombo") - self.pkgTabCombo = self.tree.get_widget("packageTabCombo") - - for c in (self.systemTabCombo, self.pkgTabCombo): - m = c.get_model() - m.clear() - for i in (_("Top"), _("Bottom"), _("Left"), _("Right")): - m.append((i,)) - - self.systemTabCombo.set_active(int(self.cfg.get("systemTabPos", section = "GUI"))-1) - self.pkgTabCombo.set_active(int(self.cfg.get("packageTabPos", section = "GUI"))-1) - - self.window.show_all() - - def _save(self): - """Sets all options in the Config-instance.""" - - for box, val in self.checkboxes.iteritems(): - if isinstance(val, tuple): - self.cfg.set_boolean(val[0], self.tree.get_widget(box).get_active(), section = val[1]) - else: - self.cfg.set_boolean(val, self.tree.get_widget(box).get_active()) - - for edit, val in self.edits.iteritems(): - if isinstance(val,tuple): - self.cfg.set(val[0], self.tree.get_widget(edit).get_text(), section = val[1]) - else: - self.cfg.set(val,self.tree.get_widget(edit).get_text()) - - if system.has_set_support(): - self.cfg.set("updatesets", ", ".join(sorted(name for enabled, markup, descr, name in self.setList.get_model() if enabled))) - - font = self.consoleFontBtn.get_font_name() - self.cfg.set("consolefont", font, section = "GUI") - self.console_fn(font) - - self.cfg.set("titlelength", str(self.titleLengthSpinBtn.get_value_as_int()), section = "GUI") - - pkgPos = self.pkgTabCombo.get_active()+1 - sysPos = self.systemTabCombo.get_active()+1 - - self.cfg.set("packageTabPos", str(pkgPos), section = "GUI") - self.cfg.set("systemTabPos", str(sysPos), section = "GUI") - - self.tabpos_fn(map(self.tabpos.get, (pkgPos, sysPos))) - - self.linkbtn_fn(self.cfg.get("browserCmd", section="GUI")) - - self.catmodel_fn() - - def fill_setlist (self): - store = gtk.ListStore(bool, str, str, str) - - enabled = [x.strip() for x in self.cfg.get("updatesets").split(",")] - - for set, descr in system.get_sets(description = True): - store.append([set in enabled, "%s" % set, descr, set]) - - tCell = gtk.CellRendererToggle() - tCell.set_property("activatable", True) - tCell.connect("toggled", self.cb_check_toggled) # emulate the normal toggle behavior ... - - sCell = gtk.CellRendererText() - - col = gtk.TreeViewColumn(_("Package Set"), tCell, active = 0) - col.pack_start(sCell) - col.add_attribute(sCell, "markup", 1) - self.setList.append_column(col) - - self.setList.append_column(gtk.TreeViewColumn(_("Description"), sCell, text = 2)) - - self.setList.set_model(store) - - def cb_ok_clicked(self, button): - """Saves, writes to config-file and closes the window.""" - self._save() - try: - self.cfg.write() - except IOError, e: - io_ex_dialog(e) - - self.window.destroy() + """Window displaying some preferences.""" + + # all checkboxes in the window + # widget name -> option name + checkboxes = { + "collapseCatCheck" : ("collapseCats", "GUI"), + "consoleUpdateCheck" : ("updateConsole", "GUI"), + "debugCheck" : "debug", + "deepCheck" : "deep", + "newUseCheck" : "newuse", + "maskPerVersionCheck" : "maskPerVersion", + "minimizeCheck" : ("hideOnMinimize", "GUI"), + "searchOnTypeCheck" : ("searchOnType", "GUI"), + "showSlotsCheck" : ("showSlots", "GUI"), + "systrayCheck" : ("showSystray", "GUI"), + "testPerVersionCheck" : "keywordPerVersion", + "titleUpdateCheck" : ("updateTitle", "GUI"), + "usePerVersionCheck" : "usePerVersion" + } + + # all edits in the window + # widget name -> option name + edits = { + "maskFileEdit" : "maskFile", + "testFileEdit" : "keywordFile", + "useFileEdit" : "useFile", + "syncCommandEdit" : "syncCommand", + "browserEdit" : ("browserCmd", "GUI") + } + + # the mappings for the tabpos combos + tabpos = { + 1 : gtk.POS_TOP, + 2 : gtk.POS_BOTTOM, + 3 : gtk.POS_LEFT, + 4 : gtk.POS_RIGHT + } + + def __init__ (self, parent, cfg, console_fn, linkbtn_fn, tabpos_fn, catmodel_fn): + """Constructor. + + @param parent: parent window + @type parent: gtk.Window + @param cfg: configuration object + @type cfg: gui_helper.Config + @param console_fn: function to call to set the console font + @type console_fn: function(string) + @param linkbtn_fn: function to call to set the linkbutton behavior + @type linkbtn_fn: function(string) + @param tabpos_fn: function to call to set the tabposition of the notebooks + @type tabpos_fn: function(gtk.ComboBox,int) + @param catmodel_fn: function to call to set the model of the cat list (collapsed/not collapsed) + @type catmodel_fn: function()""" + + AbstractDialog.__init__(self, parent) + + # our config + self.cfg = cfg + + # the setter functions + self.console_fn = console_fn + self.linkbtn_fn = linkbtn_fn + self.tabpos_fn = tabpos_fn + self.catmodel_fn = catmodel_fn + + # set the bg-color of the hint + hintEB = self.tree.get_widget("hintEB") + hintEB.modify_bg(gtk.STATE_NORMAL, get_color(self.cfg, "prefhint")) + + # the checkboxes + for box, val in self.checkboxes.iteritems(): + if isinstance(val, tuple): + self.tree.get_widget(box).\ + set_active(self.cfg.get_boolean(val[0], section = val[1])) + else: + self.tree.get_widget(box).\ + set_active(self.cfg.get_boolean(val)) + + # the edits + for edit, val in self.edits.iteritems(): + if isinstance(val,tuple): + self.tree.get_widget(edit).\ + set_text(self.cfg.get(val[0], section = val[1])) + else: + self.tree.get_widget(edit).\ + set_text(self.cfg.get(val)) + + # the set list + self.setList = self.tree.get_widget("setList") + if system.has_set_support(): + self.fill_setlist() + self.tree.get_widget("setFrame").show() + + # the console font button + self.consoleFontBtn = self.tree.get_widget("consoleFontBtn") + self.consoleFontBtn.set_font_name(self.cfg.get("consolefont", section = "GUI")) + + # the console title length spin button + self.titleLengthSpinBtn = self.tree.get_widget("titleLengthSpinBtn") + self.titleLengthSpinBtn.set_value(int(self.cfg.get("titlelength", section = "GUI"))) + + # the comboboxes + self.systemTabCombo = self.tree.get_widget("systemTabCombo") + self.pkgTabCombo = self.tree.get_widget("packageTabCombo") + + for c in (self.systemTabCombo, self.pkgTabCombo): + m = c.get_model() + m.clear() + for i in (_("Top"), _("Bottom"), _("Left"), _("Right")): + m.append((i,)) + + self.systemTabCombo.set_active(int(self.cfg.get("systemTabPos", section = "GUI"))-1) + self.pkgTabCombo.set_active(int(self.cfg.get("packageTabPos", section = "GUI"))-1) + + self.window.show_all() + + def _save(self): + """Sets all options in the Config-instance.""" + + for box, val in self.checkboxes.iteritems(): + if isinstance(val, tuple): + self.cfg.set_boolean(val[0], self.tree.get_widget(box).get_active(), section = val[1]) + else: + self.cfg.set_boolean(val, self.tree.get_widget(box).get_active()) + + for edit, val in self.edits.iteritems(): + if isinstance(val,tuple): + self.cfg.set(val[0], self.tree.get_widget(edit).get_text(), section = val[1]) + else: + self.cfg.set(val,self.tree.get_widget(edit).get_text()) + + if system.has_set_support(): + self.cfg.set("updatesets", ", ".join(sorted(name for enabled, markup, descr, name in self.setList.get_model() if enabled))) + + font = self.consoleFontBtn.get_font_name() + self.cfg.set("consolefont", font, section = "GUI") + self.console_fn(font) + + self.cfg.set("titlelength", str(self.titleLengthSpinBtn.get_value_as_int()), section = "GUI") + + pkgPos = self.pkgTabCombo.get_active()+1 + sysPos = self.systemTabCombo.get_active()+1 + + self.cfg.set("packageTabPos", str(pkgPos), section = "GUI") + self.cfg.set("systemTabPos", str(sysPos), section = "GUI") + + self.tabpos_fn(map(self.tabpos.get, (pkgPos, sysPos))) + + self.linkbtn_fn(self.cfg.get("browserCmd", section="GUI")) + + self.catmodel_fn() + + def fill_setlist (self): + store = gtk.ListStore(bool, str, str, str) + + enabled = [x.strip() for x in self.cfg.get("updatesets").split(",")] + + for set, descr in system.get_sets(description = True): + store.append([set in enabled, "%s" % set, descr, set]) + + tCell = gtk.CellRendererToggle() + tCell.set_property("activatable", True) + tCell.connect("toggled", self.cb_check_toggled) # emulate the normal toggle behavior ... + + sCell = gtk.CellRendererText() + + col = gtk.TreeViewColumn(_("Package Set"), tCell, active = 0) + col.pack_start(sCell) + col.add_attribute(sCell, "markup", 1) + self.setList.append_column(col) + + self.setList.append_column(gtk.TreeViewColumn(_("Description"), sCell, text = 2)) + + self.setList.set_model(store) + + def cb_ok_clicked(self, button): + """Saves, writes to config-file and closes the window.""" + self._save() + try: + self.cfg.write() + except IOError, e: + io_ex_dialog(e) + + self.window.destroy() - def cb_cancel_clicked (self, button): - """Just closes - w/o saving.""" - self.window.destroy() + def cb_cancel_clicked (self, button): + """Just closes - w/o saving.""" + self.window.destroy() - def cb_check_toggled (self, cell, path): - # for whatever reason we have to define normal toggle behavior explicitly - store = self.setList.get_model() - store[path][0] = not store[path][0] - return True + def cb_check_toggled (self, cell, path): + # for whatever reason we have to define normal toggle behavior explicitly + store = self.setList.get_model() + store[path][0] = not store[path][0] + return True diff --git a/portato/gui/windows/search.py b/portato/gui/windows/search.py index e776dd1..415cbfe 100644 --- a/portato/gui/windows/search.py +++ b/portato/gui/windows/search.py @@ -17,59 +17,59 @@ from .basic import AbstractDialog from ...helper import debug class SearchWindow (AbstractDialog): - """A window showing the results of a search process.""" - - def __init__ (self, parent, list, jump_to): - """Constructor. + """A window showing the results of a search process.""" + + def __init__ (self, parent, list, jump_to): + """Constructor. - @param parent: parent-window - @type parent: gtk.Window - @param list: list of results to show - @type list: string[] - @param jump_to: function to call if "OK"-Button is hit - @type jump_to: function(string)""" - - AbstractDialog.__init__(self, parent) - - self.jump_to = jump_to # function to call for jumping - self.list = list - self.list.sort() - - # combo box - self.searchList = self.tree.get_widget("searchList") - self.build_sort_list() - self.searchList.get_selection().select_path(0) + @param parent: parent-window + @type parent: gtk.Window + @param list: list of results to show + @type list: string[] + @param jump_to: function to call if "OK"-Button is hit + @type jump_to: function(string)""" + + AbstractDialog.__init__(self, parent) + + self.jump_to = jump_to # function to call for jumping + self.list = list + self.list.sort() + + # combo box + self.searchList = self.tree.get_widget("searchList") + self.build_sort_list() + self.searchList.get_selection().select_path(0) - # finished --> show - self.window.show_all() + # finished --> show + self.window.show_all() - def build_sort_list (self): - """Builds the sort list.""" - - store = gtk.ListStore(str) - self.searchList.set_model(store) + def build_sort_list (self): + """Builds the sort list.""" + + store = gtk.ListStore(str) + self.searchList.set_model(store) - # build categories - for p in self.list: - store.append(["%s/%s" % tuple(p.split("/"))]) + # build categories + for p in self.list: + store.append(["%s/%s" % tuple(p.split("/"))]) - cell = gtk.CellRendererText() - col = gtk.TreeViewColumn(_("Results"), cell, markup = 0) - self.searchList.append_column(col) + cell = gtk.CellRendererText() + col = gtk.TreeViewColumn(_("Results"), cell, markup = 0) + self.searchList.append_column(col) - def ok (self, *args): - self.jump() - self.close() - - def jump (self, *args): - model, iter = self.searchList.get_selection().get_selected() - self.jump_to(self.list[model.get_path(iter)[0]]) + def ok (self, *args): + self.jump() + self.close() + + def jump (self, *args): + model, iter = self.searchList.get_selection().get_selected() + self.jump_to(self.list[model.get_path(iter)[0]]) - def cb_key_pressed_combo (self, widget, event): - """Emulates a ok-button-click.""" - keyname = gtk.gdk.keyval_name(event.keyval) - if keyname == "Return": # take it as an "OK" if Enter is pressed - self.jump() - return True - else: - return False + def cb_key_pressed_combo (self, widget, event): + """Emulates a ok-button-click.""" + keyname = gtk.gdk.keyval_name(event.keyval) + if keyname == "Return": # take it as an "OK" if Enter is pressed + self.jump() + return True + else: + return False diff --git a/portato/gui/windows/splash.py b/portato/gui/windows/splash.py index df19a9b..c27f74f 100644 --- a/portato/gui/windows/splash.py +++ b/portato/gui/windows/splash.py @@ -18,33 +18,33 @@ from .basic import Window from ...constants import VERSION, APP_ICON class SplashScreen (Window): - - def __init__ (self, startStr = ""): - Window.__init__(self) - - self.image = self.tree.get_widget("image") - self.genLabel = self.tree.get_widget("generalLabel") - self.descrLabel = self.tree.get_widget("descrLabel") - - self.image.set_from_file(APP_ICON) - self.genLabel.set_label("Portato %s ..." % VERSION) - - self.set_descr(startStr) - - def set_descr (self, string): - self.descrLabel.set_label(_("... is starting up: %s") % string) - self.do_iteration() - - def do_iteration (self): - while gtk.events_pending(): - gtk.main_iteration() - - def show (self): - self.window.show_all() - self.do_iteration() - - def hide (self): - self.window.hide() - self.do_iteration() - - __call__ = set_descr + + def __init__ (self, startStr = ""): + Window.__init__(self) + + self.image = self.tree.get_widget("image") + self.genLabel = self.tree.get_widget("generalLabel") + self.descrLabel = self.tree.get_widget("descrLabel") + + self.image.set_from_file(APP_ICON) + self.genLabel.set_label("Portato %s ..." % VERSION) + + self.set_descr(startStr) + + def set_descr (self, string): + self.descrLabel.set_label(_("... is starting up: %s") % string) + self.do_iteration() + + def do_iteration (self): + while gtk.events_pending(): + gtk.main_iteration() + + def show (self): + self.window.show_all() + self.do_iteration() + + def hide (self): + self.window.hide() + self.do_iteration() + + __call__ = set_descr diff --git a/portato/gui/windows/update.py b/portato/gui/windows/update.py index e369c49..297f666 100644 --- a/portato/gui/windows/update.py +++ b/portato/gui/windows/update.py @@ -21,97 +21,97 @@ from ...helper import debug class UpdateWindow (AbstractDialog): - def __init__ (self, parent, packages, queue, jump_to): - AbstractDialog.__init__(self, parent) - - self.queue = queue - self.jump = jump_to - - self.packages = system.sort_package_list(packages) - - self.build_list() - - self.window.show_all() - - def build_list (self): - - store = gtk.ListStore(bool, str) - self.view = self.tree.get_widget("packageList") - self.view.set_model(store) - - cell = gtk.CellRendererText() - tCell = gtk.CellRendererToggle() - tCell.set_property("activatable", True) - tCell.connect("toggled", self.cb_check_toggled) # emulate the normal toggle behavior ... - - self.view.append_column(gtk.TreeViewColumn(_("Enabled"), tCell, active = 0)) - self.view.append_column(gtk.TreeViewColumn(_("Package"), cell, text = 1)) - - for p in self.packages: - store.append([False, p.get_cpv()]) - - def cb_set_size (self, *args): - """ - This callback is called shortly before drawing. - It calculates the optimal size of the window. - The optimum is defined as: as large as possible w/o scrollbars - """ - - bb = self.tree.get_widget("updateBB") - vals = (self.view.get_vadjustment().upper+bb.size_request()[1]+10, # max size of list + size of BB + constant - self.parent.get_size()[1]) # size of the parent -> maximum size - debug("Size values for the list and for the parent: %d / %d", *vals) - val = int(min(vals)) - debug("Minimum value: %d", val) - self.window.set_geometry_hints(self.window, min_height = val) - - def cb_select_all_clicked (self, btn): - model = self.view.get_model() - iter = model.get_iter_first() - - while iter: - model.set_value(iter, 0, True) - iter = model.iter_next(iter) - - return True - - def cb_install_clicked (self, btn): - model = self.view.get_model() - iter = model.get_iter_first() - if iter is None: return - - items = [] - while iter: - if model.get_value(iter, 0): - items.append(model.get_value(iter, 1)) - iter = model.iter_next(iter) - - for item in items: - try: - try: - self.queue.append(item, type = "install", oneshot = True) - except PackageNotFoundException, e: - if unmask_dialog(e[0]) == gtk.RESPONSE_YES : - self.queue.append(item, type = "install", unmask = True, oneshot = True) - - except BlockedException, e: - blocked_dialog(e[0], e[1]) - - self.close() - return True - - def cb_package_selected (self, view): - sel = view.get_selection() - store, it = sel.get_selected() - if it: - package = system.new_package(store.get_value(it, 1)) - - self.jump(package.get_cp(), package.get_version()) - - return True - - def cb_check_toggled (self, cell, path): - # for whatever reason we have to define normal toggle behavior explicitly - store = self.view.get_model() - store[path][0] = not store[path][0] - return True + def __init__ (self, parent, packages, queue, jump_to): + AbstractDialog.__init__(self, parent) + + self.queue = queue + self.jump = jump_to + + self.packages = system.sort_package_list(packages) + + self.build_list() + + self.window.show_all() + + def build_list (self): + + store = gtk.ListStore(bool, str) + self.view = self.tree.get_widget("packageList") + self.view.set_model(store) + + cell = gtk.CellRendererText() + tCell = gtk.CellRendererToggle() + tCell.set_property("activatable", True) + tCell.connect("toggled", self.cb_check_toggled) # emulate the normal toggle behavior ... + + self.view.append_column(gtk.TreeViewColumn(_("Enabled"), tCell, active = 0)) + self.view.append_column(gtk.TreeViewColumn(_("Package"), cell, text = 1)) + + for p in self.packages: + store.append([False, p.get_cpv()]) + + def cb_set_size (self, *args): + """ + This callback is called shortly before drawing. + It calculates the optimal size of the window. + The optimum is defined as: as large as possible w/o scrollbars + """ + + bb = self.tree.get_widget("updateBB") + vals = (self.view.get_vadjustment().upper+bb.size_request()[1]+10, # max size of list + size of BB + constant + self.parent.get_size()[1]) # size of the parent -> maximum size + debug("Size values for the list and for the parent: %d / %d", *vals) + val = int(min(vals)) + debug("Minimum value: %d", val) + self.window.set_geometry_hints(self.window, min_height = val) + + def cb_select_all_clicked (self, btn): + model = self.view.get_model() + iter = model.get_iter_first() + + while iter: + model.set_value(iter, 0, True) + iter = model.iter_next(iter) + + return True + + def cb_install_clicked (self, btn): + model = self.view.get_model() + iter = model.get_iter_first() + if iter is None: return + + items = [] + while iter: + if model.get_value(iter, 0): + items.append(model.get_value(iter, 1)) + iter = model.iter_next(iter) + + for item in items: + try: + try: + self.queue.append(item, type = "install", oneshot = True) + except PackageNotFoundException, e: + if unmask_dialog(e[0]) == gtk.RESPONSE_YES : + self.queue.append(item, type = "install", unmask = True, oneshot = True) + + except BlockedException, e: + blocked_dialog(e[0], e[1]) + + self.close() + return True + + def cb_package_selected (self, view): + sel = view.get_selection() + store, it = sel.get_selected() + if it: + package = system.new_package(store.get_value(it, 1)) + + self.jump(package.get_cp(), package.get_version()) + + return True + + def cb_check_toggled (self, cell, path): + # for whatever reason we have to define normal toggle behavior explicitly + store = self.view.get_model() + store[path][0] = not store[path][0] + return True diff --git a/portato/gui/wrapper.py b/portato/gui/wrapper.py index 2c492d4..a160e2f 100644 --- a/portato/gui/wrapper.py +++ b/portato/gui/wrapper.py @@ -16,316 +16,316 @@ import vte from ..helper import debug class GtkTree (object): - """The implementation of the abstract tree.""" - - def __init__ (self, tree, col = 0): - """Constructor. - - @param tree: original tree - @type tree: gtk.TreeStore - @param col: the column where the cpv is stored - @type col: int""" - - self.tree = tree - self.cpv_col = col - self.emergeIt = None - self.unmergeIt = None - self.updateIt = None - - def build_append_value (self, cpv, oneshot = False, update = False, downgrade = False, version = None, useChange = []): - """ - Builds the list, which is going to be passed to append. - - @param cpv: the cpv - @type cpv: string (cpv) - @param oneshot: True if oneshot - @type oneshot: boolean - @param update: True if this is an update - @type update: boolean - @param downgrade: True if this is a downgrade - @type downgrade: boolean - @param version: the version we update from - @type version: string - @param useChange: list of changed useflags; use "-use" for removed and "+use" for added flags - @type useChange: string[] - - @returns: the created list - @rtype: list - """ - - string = "" - - if oneshot: - string += "%s" % _("oneshot") - - if update: - if oneshot: string += "; " - if version is not None: - string += "%s" % (_("updating from version %s") % version) - else: - string += "%s" % _("updating") - - elif downgrade: - if oneshot: string += "; " - if version is not None: - string += "%s" % (_("downgrading from version %s") % version) - else: - string += "%s" % _("downgrading") - - if useChange: - if update or downgrade or oneshot: string += "; " - string += "%s " % _("IUSE changes:") - useChange.sort() - string += "%s" % " ".join(useChange) - - return [cpv, string, False] - - def set_in_progress (self, it, to = True): - """ - Marks the queue where the given iterator belongs as being in progress. - - @param it: one iterator of the queue to mark to - @type it: Iterator - @param to: whether to enable or disable - @type to: boolean - """ - - iter = self.first_iter(it) - if to: - self.tree.set_value(iter, 1, "%s" % _("(In Progress)")) - else: - self.tree.set_value(iter, 1, "") - - self.tree.set_value(iter, 2, to) - - def is_in_progress (self, it): - """ - Returns whether the queue where the given iterator belongs to, is marked as "being in progress". - - @param it: the iterator - @type it: Iterator - @returns: whether the queue is marked "in progress" - @rtype: boolean - """ - return self.tree.get_value(it, 2) - - def get_emerge_it (self): - """ - Returns an iterator signaling the top of the emerge section. - - @returns: emerge-iterator - @rtype: Iterator - """ - if self.emergeIt is None: - self.emergeIt = self.append(None, ["%s" % _("Install"), "", False]) - return self.emergeIt - - def get_unmerge_it (self): - """ - Returns an iterator signaling the top of the unmerge section. - - @returns: unmerge-iterator - @rtype: Iterator - """ - if self.unmergeIt is None: - self.unmergeIt = self.append(None, ["%s" % _("Uninstall"), "", False]) - - return self.unmergeIt - - def get_update_it (self): - """ - Returns an iterator signaling the top of the update section. - - @returns: unmerge-iterator - @rtype: Iterator - """ - if self.updateIt is None: - self.updateIt = self.append(None, ["%s" % _("Update"), "", False]) - - return self.updateIt - - def first_iter (self, it): - """ - Returns the iterator at the top. - - @param it: the iterator - @type it: Iterator - @returns: the top iterator - @rtype: Iterator - """ - return self.tree.get_iter_from_string(self.tree.get_string_from_iter(it).split(":")[0]) - - def is_in (self, it, in_it): - return in_it and self.iter_equal(self.first_iter(it), in_it) - - def is_in_emerge (self, it): - """ - Checks whether an iterator is part of the "Emerge" section. - - @param it: the iterator to check - @type it: Iterator - @returns: True if the iter is part; False otherwise - @rtype: boolean - """ - return self.is_in(it, self.emergeIt) - - def is_in_unmerge (self, it): - """ - Checks whether an iterator is part of the "Unmerge" section. - - @param it: the iterator to check - @type it: Iterator - @returns: True if the iter is part; False otherwise - @rtype: boolean - """ - return self.is_in(it, self.unmergeIt) - - def is_in_update (self, it): - """ - Checks whether an iterator is part of the "Update" section. - - @param it: the iterator to check - @type it: Iterator - @returns: True if the iter is part; False otherwise - @rtype: boolean - """ - return self.is_in(it, self.updateIt) - - def iter_has_parent (self, it): - """ - Returns whether the actual iterator has a parent. - @param it: the iterator - @type it: Iterator - @returns: True if it has a parent it, else False. - @rtype: boolean - """ - return (self.tree.iter_parent(it) != None) - - def parent_iter (self, it): - """ - Returns the parent iter. - - @param it: the iterator - @type it: Iterator - @returns: Parent iterator or None if the current it has no parent. - @rtype: Iterator; None - """ - return self.tree.iter_parent(it) - - def first_child_iter (self, it): - """ - Returns the first child iter. - - @param it: the iterator - @type it: Iterator - @returns: First child iterator or None if the current it has no children. - @rtype: Iterator; None - """ - - return self.tree.iter_children(it) - - def iter_has_children (self, it): - """ - Returns whether the actual iterator has children. - - @param it: the iterator - @type it: Iterator - @returns: True if it has children, else False. - @rtype: boolean - """ - - return self.tree.iter_has_child(it) - - def next_iter (self, it): - """ - Returns the next iter. - - @param it: the iterator - @type it: Iterator - @returns: Next iterator or None if the current iter is the last one. - @rtype: Iterator; None - """ - return self.tree.iter_next(it) - - def get_value (self, it, column): - """ - Returns the value of the specific column at the given iterator. - - @param it: the iterator - @type it: Iterator - @param column: the column of the iterator from where to get the value - @type column: int - @returns: the value - @rtype: anything - """ - - return self.tree.get_value(it, column) - - def iter_equal (self, it, other_it): - """ - Checks whether to iterators are equal. - - @param it: the one iterator to compare - @type it: Iterator - @param other_it: the other iterator to compare - @type other_it: Iterator - @returns: True if both iterators are equal; False otherwise - @rtype boolean - """ - return self.tree.get_string_from_iter(it) == self.tree.get_string_from_iter(other_it) - - def append (self, parent = None, values = None): - """ - Appends some values right after the given parent. If parent is None, it is appended as the first element. - - @param parent: the iterator to append the values right after; if None it symbolizes the top - @type parent: Iterator - @param values: a list of values which are going to be appended to the tree - @type values: list - @returns: Iterator pointing to the newly appended stuff - @rtype: Iterator - """ - - return self.tree.append(parent, values) - - def remove (self, it): - """ - Removes an iterator out of the tree. - @attention: The iterator can point to anything hereafter. Do not reuse! - - @param it: iterator to remove - @type it: Iterator - """ - - if self.emergeIt and self.iter_equal(it, self.emergeIt) : self.emergeIt = None - elif self.unmergeIt and self.iter_equal(it, self.unmergeIt) : self.unmergeIt = None - elif self.updateIt and self.iter_equal(it, self.updateIt) : self.updateIt = None - - self.tree.remove(it) - - def get_original (self): - """ - Returns the original tree-object. - - @returns: original tree-object - @rtype: tree-object - """ - return self.tree - - def get_cpv_column (self): - """ - Returns the number of the column where the cpv's are stored. - - @returns: column with cpv's - @rtype: int - """ - return self.cpv_col + """The implementation of the abstract tree.""" + + def __init__ (self, tree, col = 0): + """Constructor. + + @param tree: original tree + @type tree: gtk.TreeStore + @param col: the column where the cpv is stored + @type col: int""" + + self.tree = tree + self.cpv_col = col + self.emergeIt = None + self.unmergeIt = None + self.updateIt = None + + def build_append_value (self, cpv, oneshot = False, update = False, downgrade = False, version = None, useChange = []): + """ + Builds the list, which is going to be passed to append. + + @param cpv: the cpv + @type cpv: string (cpv) + @param oneshot: True if oneshot + @type oneshot: boolean + @param update: True if this is an update + @type update: boolean + @param downgrade: True if this is a downgrade + @type downgrade: boolean + @param version: the version we update from + @type version: string + @param useChange: list of changed useflags; use "-use" for removed and "+use" for added flags + @type useChange: string[] + + @returns: the created list + @rtype: list + """ + + string = "" + + if oneshot: + string += "%s" % _("oneshot") + + if update: + if oneshot: string += "; " + if version is not None: + string += "%s" % (_("updating from version %s") % version) + else: + string += "%s" % _("updating") + + elif downgrade: + if oneshot: string += "; " + if version is not None: + string += "%s" % (_("downgrading from version %s") % version) + else: + string += "%s" % _("downgrading") + + if useChange: + if update or downgrade or oneshot: string += "; " + string += "%s " % _("IUSE changes:") + useChange.sort() + string += "%s" % " ".join(useChange) + + return [cpv, string, False] + + def set_in_progress (self, it, to = True): + """ + Marks the queue where the given iterator belongs as being in progress. + + @param it: one iterator of the queue to mark to + @type it: Iterator + @param to: whether to enable or disable + @type to: boolean + """ + + iter = self.first_iter(it) + if to: + self.tree.set_value(iter, 1, "%s" % _("(In Progress)")) + else: + self.tree.set_value(iter, 1, "") + + self.tree.set_value(iter, 2, to) + + def is_in_progress (self, it): + """ + Returns whether the queue where the given iterator belongs to, is marked as "being in progress". + + @param it: the iterator + @type it: Iterator + @returns: whether the queue is marked "in progress" + @rtype: boolean + """ + return self.tree.get_value(it, 2) + + def get_emerge_it (self): + """ + Returns an iterator signaling the top of the emerge section. + + @returns: emerge-iterator + @rtype: Iterator + """ + if self.emergeIt is None: + self.emergeIt = self.append(None, ["%s" % _("Install"), "", False]) + return self.emergeIt + + def get_unmerge_it (self): + """ + Returns an iterator signaling the top of the unmerge section. + + @returns: unmerge-iterator + @rtype: Iterator + """ + if self.unmergeIt is None: + self.unmergeIt = self.append(None, ["%s" % _("Uninstall"), "", False]) + + return self.unmergeIt + + def get_update_it (self): + """ + Returns an iterator signaling the top of the update section. + + @returns: unmerge-iterator + @rtype: Iterator + """ + if self.updateIt is None: + self.updateIt = self.append(None, ["%s" % _("Update"), "", False]) + + return self.updateIt + + def first_iter (self, it): + """ + Returns the iterator at the top. + + @param it: the iterator + @type it: Iterator + @returns: the top iterator + @rtype: Iterator + """ + return self.tree.get_iter_from_string(self.tree.get_string_from_iter(it).split(":")[0]) + + def is_in (self, it, in_it): + return in_it and self.iter_equal(self.first_iter(it), in_it) + + def is_in_emerge (self, it): + """ + Checks whether an iterator is part of the "Emerge" section. + + @param it: the iterator to check + @type it: Iterator + @returns: True if the iter is part; False otherwise + @rtype: boolean + """ + return self.is_in(it, self.emergeIt) + + def is_in_unmerge (self, it): + """ + Checks whether an iterator is part of the "Unmerge" section. + + @param it: the iterator to check + @type it: Iterator + @returns: True if the iter is part; False otherwise + @rtype: boolean + """ + return self.is_in(it, self.unmergeIt) + + def is_in_update (self, it): + """ + Checks whether an iterator is part of the "Update" section. + + @param it: the iterator to check + @type it: Iterator + @returns: True if the iter is part; False otherwise + @rtype: boolean + """ + return self.is_in(it, self.updateIt) + + def iter_has_parent (self, it): + """ + Returns whether the actual iterator has a parent. + @param it: the iterator + @type it: Iterator + @returns: True if it has a parent it, else False. + @rtype: boolean + """ + return (self.tree.iter_parent(it) != None) + + def parent_iter (self, it): + """ + Returns the parent iter. + + @param it: the iterator + @type it: Iterator + @returns: Parent iterator or None if the current it has no parent. + @rtype: Iterator; None + """ + return self.tree.iter_parent(it) + + def first_child_iter (self, it): + """ + Returns the first child iter. + + @param it: the iterator + @type it: Iterator + @returns: First child iterator or None if the current it has no children. + @rtype: Iterator; None + """ + + return self.tree.iter_children(it) + + def iter_has_children (self, it): + """ + Returns whether the actual iterator has children. + + @param it: the iterator + @type it: Iterator + @returns: True if it has children, else False. + @rtype: boolean + """ + + return self.tree.iter_has_child(it) + + def next_iter (self, it): + """ + Returns the next iter. + + @param it: the iterator + @type it: Iterator + @returns: Next iterator or None if the current iter is the last one. + @rtype: Iterator; None + """ + return self.tree.iter_next(it) + + def get_value (self, it, column): + """ + Returns the value of the specific column at the given iterator. + + @param it: the iterator + @type it: Iterator + @param column: the column of the iterator from where to get the value + @type column: int + @returns: the value + @rtype: anything + """ + + return self.tree.get_value(it, column) + + def iter_equal (self, it, other_it): + """ + Checks whether to iterators are equal. + + @param it: the one iterator to compare + @type it: Iterator + @param other_it: the other iterator to compare + @type other_it: Iterator + @returns: True if both iterators are equal; False otherwise + @rtype boolean + """ + return self.tree.get_string_from_iter(it) == self.tree.get_string_from_iter(other_it) + + def append (self, parent = None, values = None): + """ + Appends some values right after the given parent. If parent is None, it is appended as the first element. + + @param parent: the iterator to append the values right after; if None it symbolizes the top + @type parent: Iterator + @param values: a list of values which are going to be appended to the tree + @type values: list + @returns: Iterator pointing to the newly appended stuff + @rtype: Iterator + """ + + return self.tree.append(parent, values) + + def remove (self, it): + """ + Removes an iterator out of the tree. + @attention: The iterator can point to anything hereafter. Do not reuse! + + @param it: iterator to remove + @type it: Iterator + """ + + if self.emergeIt and self.iter_equal(it, self.emergeIt) : self.emergeIt = None + elif self.unmergeIt and self.iter_equal(it, self.unmergeIt) : self.unmergeIt = None + elif self.updateIt and self.iter_equal(it, self.updateIt) : self.updateIt = None + + self.tree.remove(it) + + def get_original (self): + """ + Returns the original tree-object. + + @returns: original tree-object + @rtype: tree-object + """ + return self.tree + + def get_cpv_column (self): + """ + Returns the number of the column where the cpv's are stored. + + @returns: column with cpv's + @rtype: int + """ + return self.cpv_col class GtkConsole (vte.Terminal): - """The implementation of the abstract Console for GTK.""" - - def reset (self): - """ - Resets the terminal. - """ - vte.Terminal.reset(self, True, True) + """The implementation of the abstract Console for GTK.""" + + def reset (self): + """ + Resets the terminal. + """ + vte.Terminal.reset(self, True, True) diff --git a/portato/helper.py b/portato/helper.py index 145716e..2363adb 100644 --- a/portato/helper.py +++ b/portato/helper.py @@ -17,126 +17,126 @@ from __future__ import absolute_import import os, signal, logging, grp -debug = logging.getLogger("portatoLogger").debug -info = logging.getLogger("portatoLogger").info -warning = logging.getLogger("portatoLogger").warning -error = logging.getLogger("portatoLogger").error -critical = logging.getLogger("portatoLogger").critical +debug = logging.getLogger("portatoLogger").debug +info = logging.getLogger("portatoLogger").info +warning = logging.getLogger("portatoLogger").warning +error = logging.getLogger("portatoLogger").error +critical = logging.getLogger("portatoLogger").critical def N_ (s): - return s + return s def set_log_level (lvl): - logging.getLogger("portatoLogger").setLevel(lvl) + logging.getLogger("portatoLogger").setLevel(lvl) def send_signal_to_group (sig): - """Sends a signal to all processes of our process group (w/o ourselves). - - @param sig: signal number to send - @type sig: int""" - - def handler (sig, stack): - """Ignores the signal exactly one time and then restores the default.""" - signal.signal(sig, signal.SIG_DFL) - - signal.signal(sig, handler) - - pgid = os.getpgrp() - os.killpg(pgid, sig) + """Sends a signal to all processes of our process group (w/o ourselves). + + @param sig: signal number to send + @type sig: int""" + + def handler (sig, stack): + """Ignores the signal exactly one time and then restores the default.""" + signal.signal(sig, signal.SIG_DFL) + + signal.signal(sig, handler) + + pgid = os.getpgrp() + os.killpg(pgid, sig) def paren_reduce(mystr): - """ - Take a string and convert all paren enclosed entities into sublists, optionally - futher splitting the list elements by spaces. - - This function is copied from portage. - - Example usage: - >>> paren_reduce('foobar foo ( bar baz )') - ['foobar', 'foo', ['bar', 'baz']] - - @param mystr: The string to reduce - @type mystr: String - @rtype: Array - @return: The reduced string in an array - """ - mylist = [] - while mystr: - left_paren = mystr.find("(") - has_left_paren = left_paren != -1 - right_paren = mystr.find(")") - has_right_paren = right_paren != -1 - if not has_left_paren and not has_right_paren: - freesec = mystr - subsec = None - tail = "" - elif mystr[0] == ")": - return [mylist,mystr[1:]] - elif has_left_paren and not has_right_paren: - error(_("Invalid dependency string")) - return [] - elif has_left_paren and left_paren < right_paren: - freesec,subsec = mystr.split("(",1) - subsec,tail = paren_reduce(subsec) - else: - subsec,tail = mystr.split(")",1) - subsec = filter(None, subsec.split(" ")) - return [mylist+subsec,tail] - mystr = tail - if freesec: - mylist = mylist + filter(None, freesec.split(" ")) - if subsec is not None: - mylist = mylist + [subsec] - return mylist + """ + Take a string and convert all paren enclosed entities into sublists, optionally + futher splitting the list elements by spaces. + + This function is copied from portage. + + Example usage: + >>> paren_reduce('foobar foo ( bar baz )') + ['foobar', 'foo', ['bar', 'baz']] + + @param mystr: The string to reduce + @type mystr: String + @rtype: Array + @return: The reduced string in an array + """ + mylist = [] + while mystr: + left_paren = mystr.find("(") + has_left_paren = left_paren != -1 + right_paren = mystr.find(")") + has_right_paren = right_paren != -1 + if not has_left_paren and not has_right_paren: + freesec = mystr + subsec = None + tail = "" + elif mystr[0] == ")": + return [mylist,mystr[1:]] + elif has_left_paren and not has_right_paren: + error(_("Invalid dependency string")) + return [] + elif has_left_paren and left_paren < right_paren: + freesec,subsec = mystr.split("(",1) + subsec,tail = paren_reduce(subsec) + else: + subsec,tail = mystr.split(")",1) + subsec = filter(None, subsec.split(" ")) + return [mylist+subsec,tail] + mystr = tail + if freesec: + mylist = mylist + filter(None, freesec.split(" ")) + if subsec is not None: + mylist = mylist + [subsec] + return mylist def flatten (listOfLists): - """Flattens the given list of lists. + """Flattens the given list of lists. - @param listOfLists: the list of lists to flatten - @type listOfLists: list of lists - @returns: flattend list - @rtype: list""" + @param listOfLists: the list of lists to flatten + @type listOfLists: list of lists + @returns: flattend list + @rtype: list""" - if not isinstance(listOfLists, list): - return [listOfLists] + if not isinstance(listOfLists, list): + return [listOfLists] - ret = [] - for r in listOfLists: - ret.extend(flatten(r)) + ret = [] + for r in listOfLists: + ret.extend(flatten(r)) - return ret + return ret def unique_array(s): - """Stolen from portage_utils: - lifted from python cookbook, credit: Tim Peters - Return a list of the elements in s in arbitrary order, sans duplicates""" - # assume all elements are hashable, if so, it's linear - try: - return list(set(s)) - except TypeError: - pass - - # so much for linear. abuse sort. - try: - t = list(s) - t.sort() - except TypeError: - pass - else: - n = len(s) - assert n > 0 - last = t[0] - lasti = i = 1 - while i < n: - if t[i] != last: - t[lasti] = last = t[i] - lasti += 1 - i += 1 - return t[:lasti] - - # blah. back to original portage.unique_array - u = [] - for x in s: - if x not in u: - u.append(x) - return u + """Stolen from portage_utils: + lifted from python cookbook, credit: Tim Peters + Return a list of the elements in s in arbitrary order, sans duplicates""" + # assume all elements are hashable, if so, it's linear + try: + return list(set(s)) + except TypeError: + pass + + # so much for linear. abuse sort. + try: + t = list(s) + t.sort() + except TypeError: + pass + else: + n = len(s) + assert n > 0 + last = t[0] + lasti = i = 1 + while i < n: + if t[i] != last: + t[lasti] = last = t[i] + lasti += 1 + i += 1 + return t[:lasti] + + # blah. back to original portage.unique_array + u = [] + for x in s: + if x not in u: + u.append(x) + return u diff --git a/portato/plistener.py b/portato/plistener.py index fde54fc..6eb476e 100644 --- a/portato/plistener.py +++ b/portato/plistener.py @@ -16,120 +16,120 @@ import os from subprocess import Popen try: - import pynotify + import pynotify except ImportError: - pynotify = None + pynotify = None from .constants import APP from .helper import debug, warning class PListener (object): - """This class handles the communication between the "listener" and the GUI. - This listener starts programs as the user while the GUI runs as root. - - @ivar _recv: listener socket - @type _recv: int - @ivar _send: sender socket - @type _send: int""" - - def set_recv (self, mem, sig, rw): - self._mem = mem - self._sig = sig - self._rw = rw - - while True: - try: - try: - self._sig.P() - self._rw.P() - len = self._mem.read(NumberOfBytes = 4) - string = self._mem.read(NumberOfBytes = int(len), offset = 4) - finally: - self._rw.V() - - data = string.split("\0") - debug("Listner received: %s", data) - - if data[0] == "notify": - self.do_notify(*data[1:]) - elif data[0] == "cmd": - self.do_cmd(data[1:]) - elif data[0] == "close": - break - except KeyboardInterrupt: - debug("Got KeyboardInterrupt. Aborting.") - break - - self._mem.remove() - self._sig.remove() - self._rw.remove() - - self._mem = None - self._sig = None - self._rw = None - - def do_cmd (self, cmdlist): - """Starts a command as the user. - - @param cmdlist: list of command (options) - @type cmdlist: string[]""" - - Popen(cmdlist) - - def do_notify(self, base, descr, icon, urgency = None): - """Displays a notify. - This will do nothing if pynotify is not present and/or root is running the listener.""" - - if pynotify and os.getuid() != 0: - if not pynotify.is_initted(): - pynotify.init(APP) - - n = pynotify.Notification(base, descr, icon) - if urgency is not None and urgency != "": - n.set_urgency(int(urgency)) - n.show() - - def set_send (self, mem = None, sig = None, rw = None): - if mem is None or sig is None or rw is None: - warning(_("Listener has not been started.")) - self._mem = None - self._sig = None - self._rw = None - else: - import shm_wrapper as shm - - self._mem = shm.SharedMemoryHandle(mem) - self._sig = shm.SemaphoreHandle(sig) - self._rw = shm.SemaphoreHandle(rw) - - def __send (self, string): - self._rw.P() - self._sig.Z() - try: - self._mem.write("%4d%s" % (len(string), string)) - self._sig.V() - finally: - self._rw.V() - - def send_notify (self, base = "", descr = "", icon = "", urgency = None): - if self._sig is None: - self.do_notify(base, descr, icon, urgency) - else: - string = "\0".join(["notify", base, descr, icon]) - - if urgency is not None: - string += "\0%d" % urgency - else: - string += "\0" - - self.__send(string) - - def send_cmd (self, cmdlist): - if self._sig is None: - self.do_cmd(cmdlist) - else: - self.__send("\0".join(["cmd"] +cmdlist)) - - def close (self): - if self._sig is not None: - self.__send("close") + """This class handles the communication between the "listener" and the GUI. + This listener starts programs as the user while the GUI runs as root. + + @ivar _recv: listener socket + @type _recv: int + @ivar _send: sender socket + @type _send: int""" + + def set_recv (self, mem, sig, rw): + self._mem = mem + self._sig = sig + self._rw = rw + + while True: + try: + try: + self._sig.P() + self._rw.P() + len = self._mem.read(NumberOfBytes = 4) + string = self._mem.read(NumberOfBytes = int(len), offset = 4) + finally: + self._rw.V() + + data = string.split("\0") + debug("Listner received: %s", data) + + if data[0] == "notify": + self.do_notify(*data[1:]) + elif data[0] == "cmd": + self.do_cmd(data[1:]) + elif data[0] == "close": + break + except KeyboardInterrupt: + debug("Got KeyboardInterrupt. Aborting.") + break + + self._mem.remove() + self._sig.remove() + self._rw.remove() + + self._mem = None + self._sig = None + self._rw = None + + def do_cmd (self, cmdlist): + """Starts a command as the user. + + @param cmdlist: list of command (options) + @type cmdlist: string[]""" + + Popen(cmdlist) + + def do_notify(self, base, descr, icon, urgency = None): + """Displays a notify. + This will do nothing if pynotify is not present and/or root is running the listener.""" + + if pynotify and os.getuid() != 0: + if not pynotify.is_initted(): + pynotify.init(APP) + + n = pynotify.Notification(base, descr, icon) + if urgency is not None and urgency != "": + n.set_urgency(int(urgency)) + n.show() + + def set_send (self, mem = None, sig = None, rw = None): + if mem is None or sig is None or rw is None: + warning(_("Listener has not been started.")) + self._mem = None + self._sig = None + self._rw = None + else: + import shm_wrapper as shm + + self._mem = shm.SharedMemoryHandle(mem) + self._sig = shm.SemaphoreHandle(sig) + self._rw = shm.SemaphoreHandle(rw) + + def __send (self, string): + self._rw.P() + self._sig.Z() + try: + self._mem.write("%4d%s" % (len(string), string)) + self._sig.V() + finally: + self._rw.V() + + def send_notify (self, base = "", descr = "", icon = "", urgency = None): + if self._sig is None: + self.do_notify(base, descr, icon, urgency) + else: + string = "\0".join(["notify", base, descr, icon]) + + if urgency is not None: + string += "\0%d" % urgency + else: + string += "\0" + + self.__send(string) + + def send_cmd (self, cmdlist): + if self._sig is None: + self.do_cmd(cmdlist) + else: + self.__send("\0".join(["cmd"] +cmdlist)) + + def close (self): + if self._sig is not None: + self.__send("close") diff --git a/portato/plugin.py b/portato/plugin.py index befc06d..34004cb 100644 --- a/portato/plugin.py +++ b/portato/plugin.py @@ -29,524 +29,524 @@ from .backend import system from . import plugins as plugin_module class PluginLoadException (Exception): - """ - Exception signaling a failed plugin loading. - """ - pass + """ + Exception signaling a failed plugin loading. + """ + pass class Menu (object): - """ - One single menu entry. + """ + One single menu entry. - :IVariables: + :IVariables: - label : string - The label of the entry. Can have underscores to define the shortcut. + label : string + The label of the entry. Can have underscores to define the shortcut. - call - The function to call, if the entry is clicked. - """ - __slots__ = ("label", "call") + call + The function to call, if the entry is clicked. + """ + __slots__ = ("label", "call") - def __init__ (self, label, call): - self.label = label - self.call = call + def __init__ (self, label, call): + self.label = label + self.call = call class Call (object): - """ - This class represents an object, which is attached to a specified hook. + """ + This class represents an object, which is attached to a specified hook. - :IVariables: + :IVariables: - plugin : `Plugin` - The plugin where this call belongs to. + plugin : `Plugin` + The plugin where this call belongs to. - hook : string - The name of the corresponding hook. + hook : string + The name of the corresponding hook. - call - The function to call. + call + The function to call. - type : string - This is either ``before``, ``after`` or ``override`` and defines the type of the call: + type : string + This is either ``before``, ``after`` or ``override`` and defines the type of the call: - 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 + 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 - Default: ``before`` + Default: ``before`` - 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") + 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, plugin, hook, call, type = "before", dep = None): - self.plugin = plugin - self.hook = hook - self.call = call - self.type = type - self.dep = dep + 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 class Hook (object): - """ - Representing a hook with all the `Call` s for the different types. - """ - - __slots__ = ("before", "override", "after") + """ + Representing a hook with all the `Call` s for the different types. + """ + + __slots__ = ("before", "override", "after") - def __init__ (self): - self.before = [] - self.override = None - self.after = [] + def __init__ (self): + self.before = [] + self.override = None + self.after = [] 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. - - :CVariables: - - STAT_DISABLED : status - Status: Disabled. - - STAT_TEMP_ENABLED : status - Status: Enabled for this session only. - - STAT_ENABLED : status - Status: Enabled. - - STAT_TEMP_DISABLED : status - Status: Disabled for this session only. - - STAT_HARD_DISABLED : status - Status: Forced disabled by program (i.e. because of errors in the plugin). - """ - - (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 - - 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 - - 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__", "") - - 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 - """ - if hasattr(self, "__dependency__"): - return iter(self.__dependency__) - else: - return [] - - @property - def enabled (self): - """ - Returns ``True`` if the plugin is enabled. - - :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. - - :see: `Menu` - """ - self.__menus.append(Menu(label, callable)) - - def add_call (self, hook, callable, type = "before", dep = None): - """ - Adds a new call for this plugin. - - :see: `Call` - """ - self.__calls.append(Call(self, hook, callable, type, dep)) + """ + 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. + + :CVariables: + + STAT_DISABLED : status + Status: Disabled. + + STAT_TEMP_ENABLED : status + Status: Enabled for this session only. + + STAT_ENABLED : status + Status: Enabled. + + STAT_TEMP_DISABLED : status + Status: Disabled for this session only. + + STAT_HARD_DISABLED : status + Status: Forced disabled by program (i.e. because of errors in the plugin). + """ + + (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 + + 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 + + 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__", "") + + 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 + """ + if hasattr(self, "__dependency__"): + return iter(self.__dependency__) + else: + return [] + + @property + def enabled (self): + """ + Returns ``True`` if the plugin is enabled. + + :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. + + :see: `Menu` + """ + self.__menus.append(Menu(label, callable)) + + def add_call (self, hook, callable, type = "before", dep = None): + """ + Adds a new call for this plugin. + + :see: `Call` + """ + self.__calls.append(Call(self, hook, callable, type, dep)) class PluginQueue (object): - """ - Class managing and loading the plugins. - - :IVariables: - - plugins : `Plugin` [] - The list of managed plugins. - - hooks : string -> `Hook` - For each hook name map to a `Hook` object holding the corresponding `Call` objects. - """ - - def __init__ (self): - """ - Constructor. - """ - - self.plugins = [] - self.hooks = defaultdict(Hook) - - def get_plugins (self, list_disabled = True): - """ - Returns the plugins. - - :param list_disabled: Also list disabled plugins. - :type list_disabled: boolean - - :rtype: iter<`Plugin`> - """ - return (x for x in self.plugins if (x.enabled or list_disabled)) - - 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}) - - self._organize() - - def add (self, plugin, disable = False): - """ - Adds a plugin to the internal list. - - :Parameters: - - plugin : `Plugin` - ``Plugin`` subclass or instance to add. If a class is passed, it is instantiated. - - disable : boolean - Disable the plugin. - - :raise PluginLoadException: passed plugin is not of class `Plugin` - """ - - 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." - - p._init() - - self.plugins.append(p) - - 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) - - def hook (self, hook, *hargs, **hkwargs): - """ - The decorator to use in the program. - All parameters except ``hook`` are passed to plugins. - - :param hook: the name of the hook - :type hook: string - """ - - def hook_decorator (func): - """ - The real decorator. - """ - h = self.hooks[hook] - - active = Hook() - - # remove disabled - 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 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 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 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 - - return wrapper - - return hook_decorator - - def _organize (self): - """ - 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: - 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: - 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) - - self.hooks[call.hook].override = call - continue - - self._resolve_unresolved(unresolved_before, unresolved_after) - - for hook, calls in star_before.iteritems(): - self.hooks[hook].before.extend(calls) # 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, type, add): - if not list: - return - - callList = getattr(self.hooks[hook], type) - named = [x.plugin.name for x in callList] - - while list and named: - newNamed = [] # use newNamed, so in each iteration only the plugins inserted last are searched - for call in list[:]: - if call.dep in named: - callList.insert(named.index(call.dep)+add, call) - list.remove(call) - newNamed.append(call.plugin.name) - - named = newNamed - - for l in list: - callList.append(l) - info(_("Dependant '%(dep)s' for '%(hook)s' in plugin '%(plugin)s' not found! Adding nevertheless."), {"hook": hook, "plugin": l.plugin.name, "dep": l.dep}) - - for hook in before: - resolve(hook, before[hook], "before", 0) - - for hook in after: - resolve(hook, after[hook], "after", 1) + """ + Class managing and loading the plugins. + + :IVariables: + + plugins : `Plugin` [] + The list of managed plugins. + + hooks : string -> `Hook` + For each hook name map to a `Hook` object holding the corresponding `Call` objects. + """ + + def __init__ (self): + """ + Constructor. + """ + + self.plugins = [] + self.hooks = defaultdict(Hook) + + def get_plugins (self, list_disabled = True): + """ + Returns the plugins. + + :param list_disabled: Also list disabled plugins. + :type list_disabled: boolean + + :rtype: iter<`Plugin`> + """ + return (x for x in self.plugins if (x.enabled or list_disabled)) + + 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}) + + self._organize() + + def add (self, plugin, disable = False): + """ + Adds a plugin to the internal list. + + :Parameters: + + plugin : `Plugin` + ``Plugin`` subclass or instance to add. If a class is passed, it is instantiated. + + disable : boolean + Disable the plugin. + + :raise PluginLoadException: passed plugin is not of class `Plugin` + """ + + 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." + + p._init() + + self.plugins.append(p) + + 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) + + def hook (self, hook, *hargs, **hkwargs): + """ + The decorator to use in the program. + All parameters except ``hook`` are passed to plugins. + + :param hook: the name of the hook + :type hook: string + """ + + def hook_decorator (func): + """ + The real decorator. + """ + h = self.hooks[hook] + + active = Hook() + + # remove disabled + 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 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 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 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 + + return wrapper + + return hook_decorator + + def _organize (self): + """ + 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: + 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: + 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) + + self.hooks[call.hook].override = call + continue + + self._resolve_unresolved(unresolved_before, unresolved_after) + + for hook, calls in star_before.iteritems(): + self.hooks[hook].before.extend(calls) # 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, type, add): + if not list: + return + + callList = getattr(self.hooks[hook], type) + named = [x.plugin.name for x in callList] + + while list and named: + newNamed = [] # use newNamed, so in each iteration only the plugins inserted last are searched + for call in list[:]: + if call.dep in named: + callList.insert(named.index(call.dep)+add, call) + list.remove(call) + newNamed.append(call.plugin.name) + + named = newNamed + + for l in list: + callList.append(l) + info(_("Dependant '%(dep)s' for '%(hook)s' in plugin '%(plugin)s' not found! Adding nevertheless."), {"hook": hook, "plugin": l.plugin.name, "dep": l.dep}) + + for hook in before: + resolve(hook, before[hook], "before", 0) + + for hook in after: + resolve(hook, after[hook], "after", 1) __plugins = None def load_plugins(): - """ - Loads the plugins. - """ - - global __plugins - if __plugins is None: - __plugins = PluginQueue() - __plugins.load() - + """ + Loads the plugins. + """ + + global __plugins + if __plugins is None: + __plugins = PluginQueue() + __plugins.load() + def get_plugin_queue(): - """ - Returns the actual `PluginQueue`. If it is ``None``, they are not being loaded yet. + """ + Returns the actual `PluginQueue`. If it is ``None``, they are not being loaded yet. - :rtype: `PluginQueue` or ``None``""" - return __plugins + :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) + """ + 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. + """ + Registers a plugin. - :see: `PluginQueue.add` - """ - if __plugins is not None: - __plugins.add(plugin, disable) + :see: `PluginQueue.add` + """ + if __plugins is not None: + __plugins.add(plugin, disable) diff --git a/portato/session.py b/portato/session.py index 5d1a640..9f08ff7 100644 --- a/portato/session.py +++ b/portato/session.py @@ -19,104 +19,104 @@ from .constants import SESSION_DIR from .helper import debug, info class Session (object): - """ - A small class allowing to save certain states of a program. - This class works in a quite abstract manner, as it works with handlers, which - define what options to use out of the config file and how to apply them to the program. - - Note: This class currently does not work with boolean config options. If you - want to define boolean values, use 0 and 1. This is future proof. - """ - - # the current session format version - VERSION = 1 - - def __init__ (self, file): - """ - Initialize a session with a certain file inside L{SESSION_DIR}. - - @param file: the file in L{SESSION_DIR}, where the options will be saved. - """ - - self._cfg = None - self._handlers = [] - - if not (os.path.exists(SESSION_DIR) and os.path.isdir(SESSION_DIR)): - os.mkdir(SESSION_DIR) - self._cfg = ConfigParser(os.path.join(SESSION_DIR, file)) - info(_("Loading session from '%s'.") % self._cfg.file) - try: - self._cfg.parse() - except IOError, e: - if e.errno == 2: pass - else: raise - - # add version check - self.add_handler(([("version", "session")], self.check_version, lambda: self.VERSION)) - - def add_handler (self, (options, load_fn, save_fn), default = None): - """ - Adds a handler to this session. A handler is a three-tuple consisting of: - - a list of (key,section) values - - a function getting number of option arguments and applying them to the program - - a function returning the number of option return values - getting them out of the program - """ - self._handlers.append((options, load_fn, save_fn, default)) - - def load (self): - """ - Loads and applies all values of the session. - """ - for options, lfn, sfn, default in self._handlers: - try: - loaded = [self._cfg.get(*x) for x in options] - except KeyError: # does not exist -> ignore - debug("No values for %s.", options) - else: - debug("Loading %s with values %s.", options, loaded) - lfn(*loaded) - continue - - if default: - debug("Loading %s with defaults %s.", options, default) - lfn(*default) - - def save (self): - """ - Saves all options into the file. - """ - - for options, lfn, sfn, default in self._handlers: - vals = sfn() - - # map into list if necessairy - if not hasattr(vals, "__iter__"): - vals = [vals] - debug("Saving %s with values %s", options, vals) - - for value, (option, section) in zip(vals, options): - self.set(option, str(value), section) - - self._cfg.write() - - def set (self, key, value, section): - try: - self._cfg.add(key, value, section, with_blankline = False) - except SectionNotFoundException: - self._cfg.add_section(section) - self._cfg.add(key, value, section, with_blankline = False) - - def get (self, key, section): - try: - return self._cfg.get(key, section) - except KeyError: - return None - - def get_boolean (self, key, section): - try: - return self._cfg.get_boolean(key, section) - except KeyError: - return None - - def check_version (self, vers): - pass # do nothing atm + """ + A small class allowing to save certain states of a program. + This class works in a quite abstract manner, as it works with handlers, which + define what options to use out of the config file and how to apply them to the program. + + Note: This class currently does not work with boolean config options. If you + want to define boolean values, use 0 and 1. This is future proof. + """ + + # the current session format version + VERSION = 1 + + def __init__ (self, file): + """ + Initialize a session with a certain file inside L{SESSION_DIR}. + + @param file: the file in L{SESSION_DIR}, where the options will be saved. + """ + + self._cfg = None + self._handlers = [] + + if not (os.path.exists(SESSION_DIR) and os.path.isdir(SESSION_DIR)): + os.mkdir(SESSION_DIR) + self._cfg = ConfigParser(os.path.join(SESSION_DIR, file)) + info(_("Loading session from '%s'.") % self._cfg.file) + try: + self._cfg.parse() + except IOError, e: + if e.errno == 2: pass + else: raise + + # add version check + self.add_handler(([("version", "session")], self.check_version, lambda: self.VERSION)) + + def add_handler (self, (options, load_fn, save_fn), default = None): + """ + Adds a handler to this session. A handler is a three-tuple consisting of: + - a list of (key,section) values + - a function getting number of option arguments and applying them to the program + - a function returning the number of option return values - getting them out of the program + """ + self._handlers.append((options, load_fn, save_fn, default)) + + def load (self): + """ + Loads and applies all values of the session. + """ + for options, lfn, sfn, default in self._handlers: + try: + loaded = [self._cfg.get(*x) for x in options] + except KeyError: # does not exist -> ignore + debug("No values for %s.", options) + else: + debug("Loading %s with values %s.", options, loaded) + lfn(*loaded) + continue + + if default: + debug("Loading %s with defaults %s.", options, default) + lfn(*default) + + def save (self): + """ + Saves all options into the file. + """ + + for options, lfn, sfn, default in self._handlers: + vals = sfn() + + # map into list if necessairy + if not hasattr(vals, "__iter__"): + vals = [vals] + debug("Saving %s with values %s", options, vals) + + for value, (option, section) in zip(vals, options): + self.set(option, str(value), section) + + self._cfg.write() + + def set (self, key, value, section): + try: + self._cfg.add(key, value, section, with_blankline = False) + except SectionNotFoundException: + self._cfg.add_section(section) + self._cfg.add(key, value, section, with_blankline = False) + + def get (self, key, section): + try: + return self._cfg.get(key, section) + except KeyError: + return None + + def get_boolean (self, key, section): + try: + return self._cfg.get_boolean(key, section) + except KeyError: + return None + + def check_version (self, vers): + pass # do nothing atm diff --git a/portato/waiting_queue.py b/portato/waiting_queue.py index 32839d3..bc2c50c 100644 --- a/portato/waiting_queue.py +++ b/portato/waiting_queue.py @@ -17,47 +17,47 @@ from Queue import Queue class WaitingQueue (Queue): - def __init__ (self, setTrue = True, threadClass = Thread): - if not issubclass(threadClass, Thread): - raise ValueError, "Only subclasses of threading.Thread are allowed." - - Queue.__init__(self) - self.event = Event() - self.counter = 0 - self.threadClass = threadClass - - if setTrue: - self.event.set() # true at the beginning - - waitingThread = self.threadClass(name = "Waiting-Queue-Thread", target = self.runThread) - waitingThread.setDaemon(True) - waitingThread.start() - - def put (self, method, *args, **kwargs): - self.counter += 1; - - if "caller" in kwargs: - name = "Waiting Thread #%d (called by:%s)" % (self.counter, kwargs["caller"]) - del kwargs["caller"] - else: - name = "Waiting Thread #%d" % self.counter - - t = self.threadClass(name = name, target = method, args = args, kwargs = kwargs) - t.setDaemon(True) - Queue.put(self, t, False) - - def runThread (self): - while True: - self.event.wait() - t = self.get(True) - self.event.clear() - t.run() - - def next (self): - self.event.set() - - def clear (self): - self.mutex.acquire() - self.queue.clear() - self.mutex.release() - self.event.set() + def __init__ (self, setTrue = True, threadClass = Thread): + if not issubclass(threadClass, Thread): + raise ValueError, "Only subclasses of threading.Thread are allowed." + + Queue.__init__(self) + self.event = Event() + self.counter = 0 + self.threadClass = threadClass + + if setTrue: + self.event.set() # true at the beginning + + waitingThread = self.threadClass(name = "Waiting-Queue-Thread", target = self.runThread) + waitingThread.setDaemon(True) + waitingThread.start() + + def put (self, method, *args, **kwargs): + self.counter += 1; + + if "caller" in kwargs: + name = "Waiting Thread #%d (called by:%s)" % (self.counter, kwargs["caller"]) + del kwargs["caller"] + else: + name = "Waiting Thread #%d" % self.counter + + t = self.threadClass(name = name, target = method, args = args, kwargs = kwargs) + t.setDaemon(True) + Queue.put(self, t, False) + + def runThread (self): + while True: + self.event.wait() + t = self.get(True) + self.event.clear() + t.run() + + def next (self): + self.event.set() + + def clear (self): + self.mutex.acquire() + self.queue.clear() + self.mutex.release() + self.event.set() diff --git a/setup.py b/setup.py index 57d5961..6de58b0 100644 --- a/setup.py +++ b/setup.py @@ -16,23 +16,23 @@ from distutils.core import setup, Extension from portato.constants import VERSION, DATA_DIR, ICON_DIR, PLUGIN_DIR, TEMPLATE_DIR def plugin_list (*args): - """Creates a list of correct plugin pathes out of the arguments.""" - return [("plugins/%s.py" % x) for x in args] + """Creates a list of correct plugin pathes out of the arguments.""" + return [("plugins/%s.py" % x) for x in args] packages = ["portato", "portato.gui", "portato.gui.windows", "portato.plugins", "portato.backend", "portato.backend.portage"] data_files = [ - (TEMPLATE_DIR, [os.path.join("portato/gui/templates",x) for x in os.listdir("portato/gui/templates") if x.endswith(".glade")]), - (ICON_DIR, ["icons/portato-icon.png"]), - (PLUGIN_DIR, plugin_list("gpytage", "notify", "etc_proposals", "reload_portage"))] + (TEMPLATE_DIR, [os.path.join("portato/gui/templates",x) for x in os.listdir("portato/gui/templates") if x.endswith(".glade")]), + (ICON_DIR, ["icons/portato-icon.png"]), + (PLUGIN_DIR, plugin_list("gpytage", "notify", "etc_proposals", "reload_portage"))] # do the distutils setup setup(name="Portato", - version = VERSION, - description = "GTK-Frontend to Portage", - license = "GPLv2", - url = "http://portato.origo.ethz.ch/", - author = "René 'Necoro' Neumann", - author_email = "necoro@necoro.net", - packages = packages, - data_files = data_files - ) + version = VERSION, + description = "GTK-Frontend to Portage", + license = "GPLv2", + url = "http://portato.origo.ethz.ch/", + author = "René 'Necoro' Neumann", + author_email = "necoro@necoro.net", + packages = packages, + data_files = data_files + ) -- cgit v1.2.3-54-g00ecf