--- old/src/gui/modules/filelist.py 2008-08-31 10:12:08.005736747 +0100 +++ new/src/gui/modules/filelist.py 2008-08-31 10:12:07.704728119 +0100 @@ -75,12 +75,15 @@ try: lis = self.fhash[hashval] except KeyError: - # If the key isn't in the dictionary, the server - # sent us a file we didn't ask for. In this - # case, we can't create an opener for it, so just - # leave it in the cache. + # If the key isn't in the dictionary, the server sent us + # a file we didn't ask for. In this case, we can't + # create an opener for it, nor should we leave it in the + # cache. + os.remove(final_path) return + self._verify_content(lis[0], final_path) + for action in lis: action.data = self._make_opener(final_path) --- old/src/gui/modules/installupdate.py 2008-08-31 10:12:09.608632846 +0100 +++ new/src/gui/modules/installupdate.py 2008-08-31 10:12:09.416260976 +0100 @@ -28,6 +28,7 @@ import itertools import os import sys +import time from threading import Thread from urllib2 import URLError try: @@ -65,6 +66,8 @@ self.image_update = image_update self.ips_update = ips_update self.ip = None + self.progress_stop_timer_thread = False + self.progress_stop_timer_running = False w_tree_createplan = gtk.glade.XML(parent.gladefile, "createplandialog") w_tree_installupdate = gtk.glade.XML(parent.gladefile, "installupdate") w_tree_downloadingfiles = \ @@ -78,6 +81,10 @@ w_tree_createplan.get_widget("createplanprogress") self.w_createplan_textview = \ w_tree_createplan.get_widget("createplantextview") + self.w_createplan_label = \ + w_tree_createplan.get_widget("packagedependencies") + self.w_createplancancel_button = \ + w_tree_createplan.get_widget("cancelcreateplan") self.w_installupdate_dialog = w_tree_installupdate.get_widget("installupdate") self.w_summary_label = w_tree_installupdate.get_widget("packagenamelabel3") self.w_review_treeview = w_tree_installupdate.get_widget("treeview1") @@ -89,6 +96,10 @@ w_tree_downloadingfiles.get_widget("downloadprogress") self.w_installing_dialog = \ w_tree_installingdialog.get_widget("installingdialog") + self.w_installingdialog_label = \ + w_tree_installingdialog.get_widget("packagedependencies3") + self.w_installingdialog_expander = \ + w_tree_installingdialog.get_widget("expander4") self.w_installing_textview = \ w_tree_installingdialog.get_widget("installingtextview") self.w_installing_progressbar = \ @@ -135,16 +146,25 @@ list_of_packages = install_list else: list_of_packages = self.__prepare_list_of_packages() + # XXX Hidden until progress will give information about fmri + self.w_installingdialog_expander.hide() + pulse_t = Thread(target = self.__progressdialog_progress_pulse) thread = Thread(target = self.__plan_the_install_updateimage, \ args = (list_of_packages, )) + pulse_t.start() thread.start() + self.w_createplan_label.set_text(\ + self.parent._("Checking package dependencies...")) + self.w_createplancancel_button.set_sensitive(True) self.w_createplan_dialog.run() def __on_cancelcreateplan_clicked(self, widget): '''Handler for signal send by cancel button, which user might press during evaluation stage - while the dialog is creating plan''' + self.w_createplan_label.set_text(\ + self.parent._("Canceling...")) + self.w_createplancancel_button.set_sensitive(False) self.gui_thread.cancel() - self.w_createplan_dialog.destroy() def __on_cancel_button_clicked(self, widget): '''Handler for signal send by cancel button, which is available for the @@ -176,7 +196,12 @@ buf = self.w_createplan_textview.get_buffer() textiter = buf.get_end_iter() buf.insert(textiter, action) - self.w_createplan_progressbar.pulse() + + def __progressdialog_progress_pulse(self): + while not self.progress_stop_timer_thread: + gobject.idle_add(self.w_createplan_progressbar.pulse) + time.sleep(0.1) + self.progress_stop_timer_thread = False def __update_download_progress(self, cur_bytes, total_bytes): prog = float(cur_bytes)/total_bytes @@ -190,6 +215,14 @@ prog = float(current)/total self.w_installing_progressbar.set_fraction(prog) + def __update_install_pulse(self): + while not self.progress_stop_timer_thread: + self.progress_stop_timer_running = True + gobject.idle_add(self.w_installing_progressbar.pulse) + time.sleep(0.1) + self.progress_stop_timer_thread = False + self.progress_stop_timer_running = False + def __prepare_list_of_packages(self): ''' This method return the dictionary of images and newest marked packages''' @@ -294,6 +327,7 @@ image.update_optional_dependency(min_fmri) def __evaluate(self, image): + '''Code duplication from imageplan.evaluate()''' assert self.ip.state == imageplan.UNEVALUATED self.ip.progtrack.evaluate_start() @@ -324,6 +358,89 @@ self.ip.evaluate_fmri_removal(f) self.ip.progtrack.evaluate_progress() + # we now have a workable set of packages to add/upgrade/remove + # now combine all actions together to create a synthetic single + # step upgrade operation, and handle editable files moving from + # package to package. See theory comment in execute, below. + + self.ip.state = imageplan.EVALUATED_PKGS + + self.ip.removal_actions = [ (p, src, dest) + for p in self.ip.pkg_plans + for src, dest in p.gen_removal_actions() + ] + + self.ip.update_actions = [ (p, src, dest) + for p in self.ip.pkg_plans + for src, dest in p.gen_update_actions() + ] + + self.ip.install_actions = [ (p, src, dest) + for p in self.ip.pkg_plans + for src, dest in p.gen_install_actions() + ] + + self.ip.progtrack.evaluate_progress() + + # iterate over copy of removals since we're modding list + # keep track of deletion count so later use of index works + named_removals = {} + deletions = 0 + for i, a in enumerate(self.ip.removal_actions[:]): + # remove dir removals if dir is still in final image + if a[1].name == "dir" and \ + os.path.normpath(a[1].attrs["path"]) in \ + self.ip.get_directories(): + del self.ip.removal_actions[i - deletions] + deletions += 1 + continue + # store names of files being removed under own name + # or original name if specified + if a[1].name == "file": + attrs = a[1].attrs + fname = attrs.get("original_name", + "%s:%s" % (a[0].origin_fmri.get_name(), attrs["path"])) + named_removals[fname] = \ + (i - deletions, + id(self.ip.removal_actions[i-deletions][1])) + + self.ip.progtrack.evaluate_progress() + + for a in self.ip.install_actions: + # In order to handle editable files that move their path or + # change pkgs, for all new files with original_name attribute, + # make sure file isn't being removed by checking removal list. + # if it is, tag removal to save file, and install to recover + # cached version... caching is needed if directories + # are removed or don't exist yet. + if a[2].name == "file" and "original_name" in a[2].attrs and \ + a[2].attrs["original_name"] in named_removals: + cache_name = a[2].attrs["original_name"] + index = named_removals[cache_name][0] + assert(id(self.ip.removal_actions[index][1]) == + named_removals[cache_name][1]) + self.ip.removal_actions[index][1].attrs["save_file"] = \ + cache_name + a[2].attrs["save_file"] = cache_name + + self.ip.progtrack.evaluate_progress() + # Go over update actions + l_actions = self.ip.get_link_actions() + l_refresh = [] + for a in self.ip.update_actions: + # for any files being updated that are the target of + # _any_ hardlink actions, append the hardlink actions + # to the update list so that they are not broken... + if a[2].name == "file": + path = a[2].attrs["path"] + if path in l_actions: + l_refresh.extend([(a[0], l, l) for l in l_actions[path]]) + self.ip.update_actions.extend(l_refresh) + # sort actions to match needed processing order + self.ip.removal_actions.sort(key = lambda obj:obj[1], reverse=True) + self.ip.update_actions.sort(key = lambda obj:obj[2]) + self.ip.install_actions.sort(key = lambda obj:obj[2]) + self.ip.progtrack.evaluate_done() self.ip.state = imageplan.EVALUATED_OK @@ -337,6 +454,7 @@ def __evaluate_fmri(self, pfmri, image): if self.gui_thread.is_cancelled(): + self.w_createplan_dialog.destroy() return gobject.idle_add(self.__update_createplan_progress, \ self.parent._("Evaluating: %s\n") % pfmri.get_fmri()) @@ -436,29 +554,45 @@ # If it's totally absent, it will index the existing packages # so that the incremental update that follows at the end of # the function will work correctly. - self.ip.image.update_index_dir() - ind = indexer.Indexer(self.ip.image.index_dir, - CLIENT_DEFAULT_MEM_USE_KB, progtrack=self.ip.progtrack) - ind.check_index(self.ip.image.get_fmri_manifest_pairs(), - force_rebuild=False) - - for package_plan in self.ip.pkg_plans: - if self.gui_thread.is_cancelled(): - return - try: - self.__preexecute(package_plan) - except TransferTimedOutException: - self.w_downloadingfiles_dialog.hide() - self.w_networkdown_dialog.show() - return - except URLError, e: - #if e.reason[0] == 8: - self.w_downloadingfiles_dialog.hide() - self.w_networkdown_dialog.show() - return - except CancelException: - self.w_downloadingfiles_dialog.hide() - return + try: + self.ip.image.update_index_dir() + ind = indexer.Indexer(self.ip.image.index_dir, + CLIENT_DEFAULT_MEM_USE_KB, progtrack=self.ip.progtrack) + ind.check_index(self.ip.image.get_fmri_manifest_pairs(), + force_rebuild=False) + except search_errors.ProblematicPermissionsIndexException: + # ProblematicPermissionsIndexException is included here + # as there's little chance that trying again will fix + # this problem. + raise + except Exception, e: + # XXX Once we have a framework for emitting a message + # to the user in this spot in the code, we should tell + # them something has gone wrong so that we continue to + # get feedback to allow us to debug the code. + self.ip._index_exception = e + del(ind) + self.ip.image.rebuild_search_index(self.ip.progtrack) + try: + for p in self.ip.pkg_plans: + p.preexecute() + + for package_plan in self.ip.pkg_plans: + if self.gui_thread.is_cancelled(): + return + self.__download(package_plan) + except TransferTimedOutException: + self.w_downloadingfiles_dialog.hide() + self.w_networkdown_dialog.show() + return + except URLError, e: + #if e.reason[0] == 8: + self.w_downloadingfiles_dialog.hide() + self.w_networkdown_dialog.show() + return + except CancelException: + self.w_downloadingfiles_dialog.hide() + return self.ip.progtrack.download_done() self.w_downloadingfiles_dialog.hide() @@ -509,8 +643,8 @@ return self.__download_stage(True) - def __preexecute(self, package_plan): - '''Code duplication from pkg.client.PkgPlan.preexecute() except that + def __download(self, package_plan): + '''Code duplication from pkg.client.PkgPlan.download() except that pkg.gui.filelist is called instead of pkg.client.fileobject with shared cancel object - self.gui_thread that allows to cancel download operation''' @@ -523,45 +657,32 @@ _PkgPlan__prog = package_plan._PkgPlan__progtrack _PkgPlan__prog.download_start_pkg(package_plan.get_xfername()) - # retrieval step - if package_plan.destination_fmri == None: - package_plan.image.remove_install_file(package_plan.origin_fmri) - - try: - os.unlink("%s/pkg/%s/filters" % ( - package_plan.image.imgdir, - package_plan.origin_fmri.get_dir_path())) - except EnvironmentError, e: - if e.errno != errno.ENOENT: - raise - for src, dest in itertools.chain(*package_plan.actions): if dest: - dest.preinstall(package_plan, src) if dest.needsdata(src): flist.add_action(dest) - else: - src.preremove(package_plan) - # Tell flist to get any remaining files flist.flush() package_plan._PkgPlan__progtrack.download_end_pkg() - def __rebuild_index(self, pargs): + def __rebuild_index(self): '''Code duplication from pkg(1): Forcibly rebuild the search indexes. Will remove existing indexes and build new ones from scratch.''' quiet = False try: - self.ip.image.rebuild_search_index(self.ip.image.progtrack) + self.ip.image.rebuild_search_index(self.ip.progtrack) except search_errors.InconsistentIndexException, iie: return 1 except search_errors.ProblematicPermissionsIndexException, ppie: return 1 def __installation_stage(self): + '''Code duplication from imageplan.py def execute(self)''' self.gui_thread.run() + self.w_installingdialog_label.set_text(self.parent._( \ + "Installing Packages...")) self.w_installing_dialog.show() self.ip.state = imageplan.PREEXECUTED_OK @@ -570,56 +691,23 @@ self.ip.progtrack.actions_done() return - actions = [ (p, src, dest) - for p in self.ip.pkg_plans - for src, dest in p.gen_removal_actions() - ] - - actions.sort(key = lambda obj:obj[1], reverse=True) - - self.ip.progtrack.actions_set_goal("Removal Phase", len(actions)) - for p, src, dest in actions: + # execute removals + self.ip.progtrack.actions_set_goal("Removal Phase", len(self.ip.removal_actions)) + for p, src, dest in self.ip.removal_actions: p.execute_removal(src, dest) self.ip.progtrack.actions_add_progress() - - # generate list of update actions, sort and execute - update_actions = [ (p, src, dest) - for p in self.ip.pkg_plans - for src, dest in p.gen_update_actions() - ] - - install_actions = [ (p, src, dest) - for p in self.ip.pkg_plans - for src, dest in p.gen_install_actions() - ] - - # move any user/group actions into modify list to - # permit package to add user/group and change existing - # files to that user/group in a single update - # iterate over copy since we're modify install_actions - - for a in install_actions[:]: - if a[2].name == "user" or a[2].name == "group": - update_actions.append(a) - install_actions.remove(a) - - update_actions.sort(key = lambda obj:obj[2]) - - self.ip.progtrack.actions_set_goal("Update Phase", len(update_actions)) - - for p, src, dest in update_actions: - p.execute_update(src, dest) + # execute installs + self.ip.progtrack.actions_set_goal("Install Phase", len(self.ip.install_actions)) + for p, src, dest in self.ip.install_actions: + p.execute_install(src, dest) self.ip.progtrack.actions_add_progress() - # generate list of install actions, sort and execute - - install_actions.sort(key = lambda obj:obj[2]) + # execute updates + self.ip.progtrack.actions_set_goal("Update Phase", len(self.ip.update_actions)) - self.ip.progtrack.actions_set_goal("Install Phase", len(install_actions)) - - for p, src, dest in install_actions: - p.execute_install(src, dest) + for p, src, dest in self.ip.update_actions: + p.execute_update(src, dest) self.ip.progtrack.actions_add_progress() # handle any postexecute operations @@ -628,13 +716,17 @@ p.postexecute() self.ip.state = imageplan.EXECUTED_OK - - del actions - del update_actions - del install_actions + + # reduce memory consumption + del self.ip.removal_actions + del self.ip.update_actions + del self.ip.install_actions + del self.ip.target_rem_fmris del self.ip.target_fmris - del self.ip.directories + # XXX This is accessing private member, and this fix should go + # Once we will remove code duplication. + del self.ip._ImagePlan__directories # Perform the incremental update to the search indexes # for all changed packages @@ -658,11 +750,24 @@ self.ip.progtrack.actions_set_goal("Index Phase", len(plan_info)) - self.ip.image.update_index_dir() - ind = indexer.Indexer(self.ip.image.index_dir, - CLIENT_DEFAULT_MEM_USE_KB, progtrack=self.ip.progtrack) - ind.client_update_index((self.ip.filters, plan_info)) - + try: + self.ip.image.update_index_dir() + ind = indexer.Indexer(self.ip.image.index_dir, + CLIENT_DEFAULT_MEM_USE_KB, progtrack=self.ip.progtrack) + ind.client_update_index((self.ip.filters, plan_info)) + except search_errors.ProblematicPermissionsIndexException: + # ProblematicPermissionsIndexException is included here + # as there's little chance that trying again will fix + # this problem. + raise + except Exception, e: + del(ind) + # XXX Once we have a framework for emitting a message + # to the user in this spot in the code, we should tell + # them something has gone wrong so that we continue to + # get feedback to allow us to debug the code. + self.ip.image.rebuild_search_index(self.ip.progtrack) + self.ip.progtrack.actions_done() def actions_done(self): @@ -732,6 +837,7 @@ finished. Gets information like how many packages will be updated/installed and maximum amount of data which will be downloaded. Later this information is being adjusted, while downloading''' + self.progress_stop_timer_thread = True self.w_createplan_dialog.hide() if self.gui_thread.is_cancelled(): return @@ -815,9 +921,18 @@ return def ind_output(self): + self.progress_stop_timer_thread = False + gobject.idle_add(self.__indexing_progress) return + def __indexing_progress(self): + if not self.progress_stop_timer_running: + self.w_installingdialog_label.set_text(\ + self.parent._("Creating packages index...")) + Thread(target = self.__update_install_pulse).start() + def ind_output_done(self): + self.progress_stop_timer_thread = True return @staticmethod --- old/src/gui/modules/remove.py 2008-08-31 10:12:11.327843754 +0100 +++ new/src/gui/modules/remove.py 2008-08-31 10:12:11.127103860 +0100 @@ -26,6 +26,7 @@ import gettext import sys +import time from threading import Thread try: import gobject @@ -37,6 +38,8 @@ sys.exit(1) import pkg.client.bootenv as bootenv import pkg.client.imageplan as imageplan +import pkg.search_errors as search_errors +import pkg.indexer as indexer import pkg.client.progress as progress import pkg.gui.enumerations as enumerations import pkg.gui.thread as guithread @@ -53,6 +56,8 @@ #This is hack since we should show proper dialog. self.error = None self.ip = None + self.progress_stop_timer_thread = False + self.progress_stop_timer_running = False w_tree_createplan = gtk.glade.XML(parent.gladefile, "createplandialog2") w_tree_removedialog = gtk.glade.XML(parent.gladefile, "removedialog") w_tree_removingdialog = gtk.glade.XML(parent.gladefile, "removingdialog") @@ -62,6 +67,12 @@ w_tree_createplan.get_widget("createplantextview2") self.w_createplan_progressbar = \ w_tree_createplan.get_widget("createplanprogress2") + self.w_createplan_expander = \ + w_tree_createplan.get_widget("expander7") + self.w_createplan_label = \ + w_tree_createplan.get_widget("packagedependencies5") + self.w_createplancancel_button = \ + w_tree_createplan.get_widget("cancelcreateplan2") self.w_remove_dialog = w_tree_removedialog.get_widget("removedialog") self.w_summary_label = w_tree_removedialog.get_widget("removelabel") self.w_review_treeview = w_tree_removedialog.get_widget("treeview3") @@ -70,6 +81,10 @@ w_tree_removingdialog.get_widget("removingdialog") self.w_removing_progressbar = \ w_tree_removingdialog.get_widget("removingprogress") + self.w_removingdialog_label = \ + w_tree_removingdialog.get_widget("packagedependencies4") + self.w_removingdialog_expander = \ + w_tree_removingdialog.get_widget("expander6") self.w_createplan_progressbar.set_pulse_step(0.1) remove_column = gtk.TreeViewColumn('Removed') self.w_review_treeview.append_column(remove_column) @@ -96,15 +111,25 @@ Check remove.py signals') \ % error list_of_packages = self.__prepare_list_of_packages() + # XXX Hidden until progress will give information about fmri + self.w_createplan_expander.hide() + self.w_removingdialog_expander.hide() + pulse_t = Thread(target = self.__progressdialog_progress_pulse) thread = Thread(target = self.__plan_the_removeimage, \ args = (list_of_packages, )) + pulse_t.start() thread.start() + self.w_createplan_label.set_text(\ + self.parent._("Checking package dependencies...")) + self.w_createplancancel_button.set_sensitive(True) self.w_createplan_dialog.run() return - def __on_cancelcreateplan_clicked(self, widget): + def __on_cancelcreateplan_clicked(self, widget): + self.w_createplan_label.set_text(\ + self.parent._("Canceling...")) + self.w_createplancancel_button.set_sensitive(False) self.gui_thread.cancel() - self.w_createplan_dialog.destroy() def __on_next_button_clicked(self, widget): remove_thread = Thread(target = self.__remove_stage, args = ()) @@ -112,8 +137,9 @@ def __on_cancel_button_clicked(self, widget): self.gui_thread.cancel() - self.w_remove_dialog.destroy() + self.w_remove_dialog.hide() + # XXX Not used until progress will give information about fmri def __update_createplan_progress(self, action): buf = self.w_createplan_textview.get_buffer() textiter = buf.get_end_iter() @@ -149,20 +175,20 @@ fmris = list_of_packages.get(image) for fmri in fmris: if self.gui_thread.is_cancelled(): + self.progress_stop_timer_thread = True + self.w_createplan_dialog.hide() return self.ip.propose_fmri_removal(fmri) - self.ip.state = imageplan.UNEVALUATED - self.ip.progtrack.evaluate_start() - for f in self.ip.target_rem_fmris[:]: - gobject.idle_add(self.__update_createplan_progress, \ - self.parent._("Evaluating: %s\n") % f.get_fmri()) - try: - self.ip.evaluate_fmri_removal(f) - except imageplan.NonLeafPackageException, e: + try: + self.ip.evaluate() + if self.gui_thread.is_cancelled(): + self.progress_stop_timer_thread = True + self.w_createplan_dialog.hide() + return + except imageplan.NonLeafPackageException, e: self.error = e[1] gobject.idle_add(self.ip.progtrack.evaluate_done) return - self.ip.state = imageplan.EVALUATED_OK image.imageplan = self.ip gobject.idle_add(self.ip.progtrack.evaluate_done) return @@ -170,24 +196,72 @@ def __remove_stage(self): self.w_remove_dialog.hide() self.w_removing_dialog.show() + time.sleep(0.1) self.ip.preexecute() try: be = bootenv.BootEnv(self.ip.image.get_root()) except RuntimeError: be = bootenv.BootEnvNull(self.ip.image.get_root()) try: + ret_code = 0 self.ip.execute() except RuntimeError: be.restore_install_uninstall() + except search_errors.InconsistentIndexException, e: + ret_code = 2 + except search_errors.PartialIndexingException, e: + ret_code = 2 + except search_errors.ProblematicPermissionsIndexException, e: + ret_code = 2 + except KeyError, e: + # XXX KeyError was seen while problem with + # creating index + ret_code = 2 except Exception: be.restore_install_uninstall() + self.w_removing_dialog.hide() raise + if ret_code == 2: + return_code = 0 + return_code = self.__rebuild_index() + if return_code == 1: + self.w_removing_dialog.hide() + return + if self.ip.state == imageplan.EXECUTED_OK: be.activate_install_uninstall() else: be.restore_install_uninstall() + self.w_removing_dialog.hide() + def __rebuild_index(self): + '''Code duplication from pkg(1): + Forcibly rebuild the search indexes. Will remove existing indexes + and build new ones from scratch.''' + quiet = False + + try: + self.ip.image.rebuild_search_index(self.ip.progtrack) + except search_errors.InconsistentIndexException, iie: + return 1 + except search_errors.ProblematicPermissionsIndexException, ppie: + return 1 + + def __progressdialog_progress_pulse(self): + while not self.progress_stop_timer_thread: + gobject.idle_add(self.w_createplan_progressbar.pulse) + time.sleep(0.1) + self.progress_stop_timer_thread = False + + def __removedialog_progress_pulse(self): + while not self.progress_stop_timer_thread: + self.progress_stop_timer_running = True + gobject.idle_add(self.w_removing_progressbar.pulse) + time.sleep(0.1) + self.progress_stop_timer_thread = False + self.progress_stop_timer_running = False + def cat_output_start(self): return @@ -201,6 +275,7 @@ return def eval_output_done(self): + self.progress_stop_timer_thread = True self.w_createplan_dialog.hide() if self.gui_thread.is_cancelled(): return @@ -264,6 +339,8 @@ return def act_output(self): + self.w_removingdialog_label.set_text(\ + self.parent._("Removing Packages...")) gobject.idle_add(self.__update_remove_progress, \ self.ip.progtrack.act_cur_nactions, \ self.ip.progtrack.act_goal_nactions) @@ -271,12 +348,21 @@ def act_output_done(self): if self.parent != None: - self.parent.update_package_list() - self.w_removing_dialog.hide() + gobject.idle_add(self.parent.update_package_list) + time.sleep(0.1) return def ind_output(self): + self.progress_stop_timer_thread = False + gobject.idle_add(self.__indexing_progress) return + def __indexing_progress(self): + if not self.progress_stop_timer_running: + self.w_removingdialog_label.set_text(\ + self.parent._("Creating packages index...")) + Thread(target = self.__removedialog_progress_pulse).start() + def ind_output_done(self): + self.progress_stop_timer_thread = True return --- old/src/packagemanager.py 2008-08-31 10:12:13.032917681 +0100 +++ new/src/packagemanager.py 2008-08-31 10:12:12.830715095 +0100 @@ -1680,6 +1680,9 @@ def update_package_list(self): + # Without sleeping version_installed was the one before + # operation such as install/remove/update + time.sleep(0.5) for row in self.application_list: if row[enumerations.MARK_COLUMN]: img = row[enumerations.IMAGE_OBJECT_COLUMN]