diff options
Diffstat (limited to 'portato/gui/queue.py')
-rw-r--r-- | portato/gui/queue.py | 1234 |
1 files changed, 617 insertions, 617 deletions
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()))) |