diff options
Diffstat (limited to 'geneticone')
-rw-r--r-- | geneticone/gui/gui_helper.py | 265 | ||||
-rw-r--r-- | geneticone/gui/windows.py | 115 |
2 files changed, 298 insertions, 82 deletions
diff --git a/geneticone/gui/gui_helper.py b/geneticone/gui/gui_helper.py index cb9e612..449fa9c 100644 --- a/geneticone/gui/gui_helper.py +++ b/geneticone/gui/gui_helper.py @@ -9,19 +9,22 @@ # # Written by Necoro d.M. <necoro@necoro.net> +# some backend things from geneticone import backend from geneticone.backend import flags from geneticone.helper import * + +# our dialogs import dialogs +# some stuff needed from subprocess import Popen, PIPE, STDOUT from threading import Thread from ConfigParser import SafeConfigParser - import pty -import vte class Config: + """Wrapper around a ConfigParser and for additional local configurations.""" const = { "main_sec" : "Main", "usePerVersion_opt" : "usePerVersion", @@ -35,7 +38,16 @@ class Config: } def __init__ (self, cfgFile): + """Constructor. + @attention: If cfgFile is a file, it is closed afterwards! + + @param cfgFile: path to config file or file-object of the config-file. + @type cfgFile: string or file""" + + # init ConfigParser self._cfg = SafeConfigParser() + + # set correct file-obj if not isinstance(cfgFile, file): self._file = open(cfgFile) # assume string elif cfgFile.closed: @@ -43,18 +55,41 @@ class Config: else: self._file = cfgFile + # read config self._cfg.readfp(self._file) self._file.close() + # local configs self.local = {} def get(self, name, section=const["main_sec"]): + """Gets an option. + + @param name: name of the option + @type name: string + @param section: section to look in; default is Main-Section + @type section: string + @return: the option's value + @rtype: string""" + return self._cfg.get(section, name) def get_boolean(self, name, section=const["main_sec"]): + """Gets a boolean option. + + @param name: name of the option + @type name: string + @param section: section to look in; default is Main-Section + @type section: string + @return: the option's value + @rtype: boolean""" + return self._cfg.getboolean(section, name) def modify_flags_config (self): + """Sets the internal config of the L{flags}-module. + @see: L{flags.set_config()}""" + flagCfg = { "usefile": self.get(self.const["useFile_opt"]), "usePerVersion" : self.get_boolean(self.const["usePerVersion_opt"]), @@ -65,15 +100,40 @@ class Config: flags.set_config(flagCfg) def modify_debug_config (self): + """Sets the external debug-config. + @see: L{helper.set_debug()}""" set_debug(self.get_boolean(self.const["debug_opt"])) + def modify_external_configs (self): + """Convenience function setting all external configs.""" + self.modify_debug_config() + self.modify_flags_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]: @@ -82,57 +142,74 @@ class Config: return self.local[cpv][name] def set(self, name, val, section=const["main_sec"]): + """Sets an option. + + @param name: name of the option + @type name: string + @param val: value to set the option to + @type val: string or boolean + @param section: section to look in; default is Main-Section + @type section: string""" + self._cfg.set(section, name, val) def write(self): + """Writes to the config file and modify any external configs.""" self._file = open(self._file.name,"w") self._cfg.write(self._file) - self.modify_flags_config() - self.modify_debug_config() + self.modify_external_configs() class Database: """An internal database which holds a simple dictionary cat -> [package_list].""" def __init__ (self): """Constructor.""" - self.db = {} + self._db = {} 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 = backend.find_all_packages(name = category, withVersion = False) installed = backend.find_all_installed_packages(name = category, withVersion = False) + + # cycle through packages for p in packages: list = p.split("/") cat = list[0] pkg = list[1] if p in installed: pkg += "*" - if not cat in self.db: self.db[cat] = [] - self.db[cat].append(pkg) + if not cat in self._db: self._db[cat] = [] + self._db[cat].append(pkg) - for key in self.db: - self.db[key].sort(cmp=cmp, key=str.lower) + for key in self._db: # sort alphabetically + self._db[key].sort(cmp=cmp, key=str.lower) def get_cat (self, cat): """Returns the packages in the category. + @param cat: category to return the packages from @type cat: string @return: list of packages or [] - @rtype: list of strings""" + @rtype: string[]""" + try: - return self.db[cat] + return self._db[cat] except KeyError: # cat is in category list - but not in portage debug("Catched KeyError =>", cat, "seems not to be an available category. Have you played with rsync-excludes?") return [] def reload (self, cat): """Reloads the given category. + @param cat: category @type cat: string""" - del self.db[cat] + del self._db[cat] self.populate(cat+"/") class EmergeQueue: @@ -141,22 +218,28 @@ class EmergeQueue: def __init__ (self, tree = None, console = None, db = None): """Constructor. - @param tree: Tree to append all the items to. Default: None. + @param tree: Tree to append all the items to. @type tree: gtk.TreeStore - @param console: Output is shown here. Default: None + @param console: Output is shown here. @type console: vte.Terminal @param db: A database instance. @type db: Database""" - self.mergequeue = [] - self.unmergequeue = [] - self.oneshotmerge = [] - self.iters = {} - self.deps = {} + # the different queues + self.mergequeue = [] # for emerge + self.unmergequeue = [] # for emerge -C + self.oneshotmerge = [] # for emerge --oneshot + + # dictionaries with data about the packages in the queue + self.iters = {} # iterator in the tree + self.deps = {} # all the deps of the package + + # member vars self.tree = tree self.console = console self.db = db - + + # our iterators pointing at the toplevels; they are set to None if do not have a tree if self.tree: self.emergeIt = self.tree.append(None, ["Emerge", ""]) self.unmergeIt = self.tree.append(None, ["Unmerge", ""]) @@ -164,22 +247,37 @@ class EmergeQueue: self.emergeIt = self.unmergeIt = None 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 = backend.Package(cpv) - if not pkg.is_masked() and not pkg.is_testing(allowed=True): - masked = True - else: - masked = False + masked = not (pkg.is_masked() or pkg.is_testing(allowed=True)) # <-- why am I doing this? FIXME + + # and now try to find it in portage pkg = backend.find_packages("="+cpv, masked = masked) - if pkg: + + if pkg: # gotcha pkg = pkg[0] - elif unmask: + + elif unmask: # no pkg returned, but we are allowed to unmask it pkg = backend.find_packages("="+cpv, masked = True)[0] if pkg.is_testing(allowed = True): pkg.set_testing(True) if pkg.is_masked(): pkg.set_masked() - else: + + 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, options = ""): @@ -189,16 +287,20 @@ class EmergeQueue: @type it: gtk.TreeIter @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 options: options to append to the tree + @type options: string - @raise geneticone.backend.BlockedException: When occured during dependency-calculation.""" + @raises backend.BlockedException: When occured during dependency-calculation. + @raises backend.PackageNotFoundException: If no package could be found - normally it is existing but masked.""" - # get dependencies if cpv in self.deps: - return # in list already + return # in list already and therefore it's already in the tree too try: pkg = self._get_pkg_from_cpv(cpv, unmask) - except backend.PackageNotFoundException, e: + except backend.PackageNotFoundException, e: # package not found / package is masked -> delete current tree and re-raise the exception if self.tree.iter_parent(it): while self.tree.iter_parent(it): it = self.tree.iter_parent(it) @@ -209,31 +311,39 @@ class EmergeQueue: subIt = self.tree.append(it, [cpv, "<i>"+options+"</i>"]) self.iters.update({cpv: subIt}) - deps = pkg.get_dep_packages() + # get dependencies + deps = pkg.get_dep_packages() # this might raise a BlockedException self.deps.update({cpv : deps}) # recursive call for d in deps: try: self.update_tree(subIt, d, unmask) - except backend.BlockedException, e: + except backend.BlockedException, e: # BlockedException occured -> delete current tree and re-raise exception debug("Something blocked:", e[0]) self.remove_with_children(subIt) raise e - def append (self, cpv, unmerge = False, update = False, unmask = False, oneshot = False, forceUpdate = False): + def append (self, cpv, unmerge = False, 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 unmerge: Set to True if you want to unmerge this package - else False. Default: False - @type unmerge: boolean - @param update: Set to True if a package is going to be updated (e.g. if the use-flags changed). Default: False + @param unmerge: Set to True if you want to unmerge this package - else False. + @type unmerge: boolean + @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 geneticone.backend.PackageNotFoundException: if trying to add a package which does not exist""" - if not unmerge: + if not unmerge: # emerge try: # insert dependencies pkg = self._get_pkg_from_cpv(cpv, unmask) @@ -246,38 +356,53 @@ class EmergeQueue: hasBeenInQueue = (cpv in self.mergequeue or cpv in self.oneshotmerge) parentIt = self.tree.iter_parent(self.iters[cpv]) options = "" - self.remove_with_children(self.iters[cpv], False) # this is needed to def delete everything - if hasBeenInQueue: - if not oneshot: - self.mergequeue.append(cpv) - else: - self.oneshotmerge.append(cpv) - options="oneshot" + # delete it out of the tree - but NOT the changed flags + self.remove_with_children(self.iters[cpv], removeNewFlags = False) + + if hasBeenInQueue: # package has been in queue before + options = self._queue_append(cpv, oneshot) + self.update_tree(parentIt, cpv, unmask, options = options) else: # not update - options = "" - if not oneshot: self.mergequeue.append(cpv) - else: - self.oneshotmerge.append(cpv) - options = "oneshot" - if self.emergeIt: self.update_tree(self.emergeIt, cpv, unmask, options) + options = self._queue_append(cpv, oneshot) + if self.emergeIt: + self.update_tree(self.emergeIt, cpv, unmask, options) except backend.BlockedException, e : # there is sth blocked --> call blocked_dialog blocks = e[0] dialogs.blocked_dialog(cpv, blocks) return + else: # unmerge self.unmergequeue.append(cpv) if self.unmergeIt: # update tree self.tree.append(self.unmergeIt, [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 onehost: True if this package should not be added to the world-file. + @type oneshot: boolean + + @returns: options set + @rtype: string""" + + options = "" + if not oneshot: + self.mergequeue.append(cpv) + else: + self.oneshotmerge.append(cpv) + options="oneshot" def _update_packages(self, packages, process = None): """This updates the packages-list. It simply makes the db to rebuild the specific category. @param packages: The packages which we emerged. @type packages: list of cpvs - @param process: The process we have to wait for before we can do our work. Default: None. + @param process: The process we have to wait for before we can do our work. @type process: subprocess.Popen""" if process: process.wait() @@ -304,6 +429,8 @@ class EmergeQueue: # start emerge process = Popen(["/usr/bin/python","/usr/bin/emerge"]+options+packages, stdout = slave, stderr = STDOUT, shell = False) + + # start thread waiting for the stop of emerge Thread(target=self._update_packages, args=(packages, process)).start() # remove @@ -315,27 +442,32 @@ class EmergeQueue: @param force: If False, '-pv' is send to emerge. Default: False. @type force: boolean""" - - if len(self.oneshotmerge) != 0: - # prepare package-list for oneshot + + def prepare(queue): + """Prepares the list of iterators and the list of packages.""" list = [] its = [] - for k in self.oneshotmerge: + for k in queue: list += ["="+k] its.append(self.iters[k]) + return list, its + + # oneshot-queue + if len(self.oneshotmerge) != 0: + # prepare package-list for oneshot + list, its = prepare + s = ["--oneshot"] if not force: s += ["--verbose", "--pretend"] + self._emerge(s, list, its) + # normal queue if len(self.mergequeue) != 0: # prepare package-list - list = [] - its = [] - for k in self.mergequeue: - list += ["="+k] - its.append(self.iters[k]) - + list, its = prepare + s = [] if not force: s = ["--verbose", "--pretend"] @@ -358,6 +490,13 @@ class EmergeQueue: self._emerge(s,list, [self.unmergeIt]) def remove_with_children (self, it, removeNewFlags = True): + """Convenience function which removes all children of an iterator and than the iterator itself. + + @param parentIt: The iter which to remove. + @type parentIt: gtk.TreeIter + @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) @@ -402,7 +541,7 @@ class EmergeQueue: except ValueError: debug("Catched ValueError =>", cpv, "seems not to be in merge-queue. Should be no harm.") - if removeNewFlags: + if removeNewFlags: # remove the changed flags flags.remove_new_use_flags(cpv) flags.remove_new_masked(cpv) flags.remove_new_testing(cpv) diff --git a/geneticone/gui/windows.py b/geneticone/gui/windows.py index 1b16598..a08fe16 100644 --- a/geneticone/gui/windows.py +++ b/geneticone/gui/windows.py @@ -28,22 +28,40 @@ from gui_helper import Database, Config, EmergeQueue from dialogs import * # for the terminal -import pty import vte # other from portage_util import unique_array class AbstractDialog: + """A class all our dialogs get derived from. It sets useful default vars and automatically handles the ESC-Button.""" def __init__ (self, parent, title): + """Constructor. + + @param parent: the parent window + @type parent: gtk.Window + @param title: the title of the window + @type title: string""" + + # create new self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + + # set title self.window.set_title(title) + + # set modal and transient for the parent --> you have to close this one to get back to the parent self.window.set_modal(True) self.window.set_transient_for(parent) self.window.set_destroy_with_parent(True) + + # not resizable self.window.set_resizable(False) + + # default size = (1,1) ==> as small as possible self.window.set_default_size(1,1) + + # catch the ESC-key self.window.connect("key-press-event", self.cb_key_pressed) def cb_key_pressed (self, widget, event): @@ -59,10 +77,17 @@ class AboutWindow (AbstractDialog): """A window showing the "about"-informations.""" def __init__ (self, parent): + """Constructor. + + @param parent: the parent window + @type parent: gtk.Window""" + AbstractDialog.__init__(self, parent, "About Genetic/One") + box = gtk.VBox(False) self.window.add(box) + # about label label = gtk.Label() label.set_justify(gtk.JUSTIFY_CENTER) label.set_markup(""" @@ -76,69 +101,104 @@ Copyright (C) 2006 Necoro d.M. <necoro@necoro.net> """ % VERSION) box.pack_start(label) + # button okBtn = gtk.Button("OK") okBtn.connect("clicked", lambda x: self.window.destroy()) box.pack_start(okBtn) + # finished -> show self.window.show_all() class SearchWindow (AbstractDialog): """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, "Search results") - self.list = list - self.jump_to = jump_to + self.list = list # list to show + self.jump_to = jump_to # function to call for jumping box = gtk.HBox(False) self.window.add(box) + # combo box self.combo = gtk.combo_box_new_text() for x in list: self.combo.append_text(x) - self.combo.set_active(0) - self.combo.connect("key-press-event", self.cb_key_pressed) + self.combo.set_active(0) # first item + self.combo.connect("key-press-event", self.cb_key_pressed_combo) box.pack_start(self.combo) + # ok-button okBtn = gtk.Button("OK") okBtn.connect("clicked", self.cb_ok_btn_clicked) box.pack_start(okBtn) + # finished --> show self.window.show_all() - def cb_ok_btn_clicked (self, button, data = None): + def cb_ok_btn_clicked (self, button): + """Called if the OK-Button is clicked. + Calls self.jump_to(selected_entry) and closes the window.""" self.window.destroy() self.jump_to(self.list[self.combo.get_active()]) return True - def cb_key_pressed (self, widget, event): + 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.cb_ok_btn_clicked(self,widget) return True - elif keyname == "Escape": - self.window.destroy() - return True else: return False class PreferenceWindow (AbstractDialog): + """Window displaying some preferences.""" def __init__ (self, parent, cfg): - AbstractDialog.__init__(self, parent, "Preferences") - self.window.set_resizable(True) + """Constructor. + + @param parent: parent window + @type parent: gtk.Window + @param cfg: configuration object + @type cfg: gui_helper.Config""" + AbstractDialog.__init__(self, parent, "Preferences") + self.window.set_resizable(True) # override the default of the AbstractDialog + + # our config self.cfg = cfg - box = gtk.VBox(True) + box = gtk.VBox() + box.set_spacing(5) + self.window.add(box) + # En-/Disable Debugging self.debugCb = gtk.CheckButton(label="Debugging modus") self.debugCb.set_active(self.cfg.get_boolean(self.cfg.const["debug_opt"])) box.pack_start(self.debugCb, True, True) + pHolderLabel = gtk.Label("""<u>For the following options, you might use these placeholders:</u> +<b>$(cat)</b> = category +<b>$(pkg)</b> = package-name +<b>$(cat-1)</b>/<b>$(cat-2)</b> = first/second part of the category""") + pHolderLabel.set_use_markup(True) + pHolderLabel.set_alignment(0,0) + box.pack_start(pHolderLabel) + + # The use/mask/keywording checkboxes and edits self.usePerVersionCb, self.useFileEdit = self.draw_cb_and_edit(box, "package.use", "usePerVersion_opt", "useFile_opt") self.maskPerVersionCb, self.maskFileEdit = self.draw_cb_and_edit(box, "package.mask/package.unmask", "maskPerVersion_opt", "maskFile_opt") self.testPerVersionCb, self.testFileEdit = self.draw_cb_and_edit(box, "package.keywords", "testingPerVersion_opt", "testingFile_opt") @@ -155,16 +215,32 @@ class PreferenceWindow (AbstractDialog): box.pack_start(buttonHB, True, True, 5) + # finished --> show all self.window.show_all() def draw_cb_and_edit (self, box, string, cb_opt, edit_opt): + """Draws a checkbox and an edit-field. + + @param box: box to place the both things into + @type box: gtk.Box + @param string: string to show + @type string: string + @param cb_opt: the option string for the Config.const-dict + @type cb_opt: string + @param edit_opt: the option string for the Config.const-dic + @type edit_opt: string + + @return: the checkbox and the edit-field + @rtype: (gtk.CheckButton, gtk.Edit)""" + + # check-button cb = gtk.CheckButton(label=("Add to %s on a per-version-base" % string)) cb.set_active(self.cfg.get_boolean(self.cfg.const[cb_opt])) box.pack_start(cb, True, True) + # edit with label hBox = gtk.HBox() - label = gtk.Label(("File name to use if %s is a directory:\n<small><b>$(cat)</b> = category\n<b>$(pkg)</b> = package-name\n<b>$(cat-1)</b>/<b>$(cat-2)</b> = first/second part of the category</small>" % string)) - label.set_use_markup(True) + label = gtk.Label("File name to use if %s is a directory:" % string) edit = gtk.Entry() edit.set_text(self.cfg.get(self.cfg.const[edit_opt])) hBox.pack_start(label, False) @@ -174,6 +250,7 @@ class PreferenceWindow (AbstractDialog): return (cb, edit) def _save(self): + """Sets all options in the Config-instance.""" self.cfg.set(self.cfg.const["usePerVersion_opt"], str(self.usePerVersionCb.get_active())) self.cfg.set(self.cfg.const["useFile_opt"], self.useFileEdit.get_text()) self.cfg.set(self.cfg.const["maskPerVersion_opt"], str(self.maskPerVersionCb.get_active())) @@ -183,6 +260,7 @@ class PreferenceWindow (AbstractDialog): self.cfg.set(self.cfg.const["debug_opt"], str(self.debugCb.get_active())) def cb_ok_clicked(self, button): + """Saves, writes to config-file and closes the window.""" self._save() self.cfg.write() self.window.destroy() @@ -506,8 +584,7 @@ class MainWindow: # config self.cfg = Config(CONFIG_LOCATION) - self.cfg.modify_flags_config() - self.cfg.modify_debug_config() + self.cfg.modify_external_configs() # actions needed self.emergeAction = gtk.Action("Emerge", "_Emerge", None, None) @@ -654,9 +731,9 @@ class MainWindow: ("File", None, "_File"), ("EmergeMenu", None, "_Emerge"), ("Help", None, "_?"), - ("Prefs", None, "_Preferences", None, None, lambda x,y: PreferenceWindow(self.window, self.cfg)), + ("Prefs", None, "_Preferences", None, None, lambda x: PreferenceWindow(self.window, self.cfg)), ("Close", None, "_Close", None, None, self.cb_destroy), - ("About", None, "_About", None, None, lambda x,y: AboutWindow(self.window))]) + ("About", None, "_About", None, None, lambda x: AboutWindow(self.window))]) group.add_action(self.emergeAction) group.add_action(self.unmergeAction) um.insert_action_group(group,0) |