From eee9e9abf8c98df3c1a9aeedab717e3ad2125801 Mon Sep 17 00:00:00 2001 From: Vinicius Moreira Date: Tue, 16 Jun 2020 11:41:06 -0300 Subject: [PATCH] [improvement][ui] uninstallation does not refresh the table if it succeeds --- CHANGELOG.md | 2 +- bauh/api/abstract/controller.py | 9 +++- bauh/gems/appimage/controller.py | 11 ++--- bauh/gems/arch/controller.py | 65 +++++++++++++++++++------- bauh/gems/flatpak/controller.py | 13 +++--- bauh/gems/snap/controller.py | 13 +++--- bauh/gems/web/controller.py | 10 ++-- bauh/view/core/controller.py | 47 ++++++++++++------- bauh/view/qt/apps_table.py | 12 ++--- bauh/view/qt/thread.py | 36 +++++++++------ bauh/view/qt/window.py | 78 +++++++++++++++++++++++--------- 11 files changed, 196 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a06babdd..725eda11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - AppImage - able to update AppImages with continuous releases - UI - - not performing a full table refresh after installing a new package + - not performing a full table refresh after installing and uninstalling packages - filters algorithm speed and sorting - displaying wait cursor over some components diff --git a/bauh/api/abstract/controller.py b/bauh/api/abstract/controller.py index be76b9e3..91fd2989 100644 --- a/bauh/api/abstract/controller.py +++ b/bauh/api/abstract/controller.py @@ -70,6 +70,10 @@ class TransactionResult: self.installed = installed self.removed = removed + @staticmethod + def fail() -> "TransactionResult": + return TransactionResult(success=False, installed=None, removed=None) + class SoftwareManager(ABC): @@ -146,12 +150,13 @@ class SoftwareManager(ABC): pass @abstractmethod - def uninstall(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool: + def uninstall(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher, disk_loader: DiskCacheLoader) -> TransactionResult: """ :param pkg: :param root_password: the root user password (if required) :param watcher: - :return: if the uninstall succeeded + :param disk_loader: + :return: """ pass diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py index 968224a9..ab73c02d 100644 --- a/bauh/gems/appimage/controller.py +++ b/bauh/gems/appimage/controller.py @@ -277,7 +277,7 @@ class AppImageManager(SoftwareManager): type_=MessageType.ERROR) return False else: - if self.uninstall(pkg, root_password, watcher): + if self.uninstall(pkg, root_password, watcher).success: old_release = versions.history[versions.pkg_status_idx + 1] pkg.version = old_release['0_version'] pkg.latest_version = pkg.version @@ -300,7 +300,7 @@ class AppImageManager(SoftwareManager): for req in requirements.to_upgrade: watcher.change_status("{} {} ({})...".format(self.i18n['manage_window.status.upgrading'], req.pkg.name, req.pkg.version)) - if not self.uninstall(req.pkg, root_password, watcher): + if not self.uninstall(req.pkg, root_password, watcher).success: watcher.show_message(title=self.i18n['error'], body=self.i18n['appimage.error.uninstall_current_version'], type_=MessageType.ERROR) @@ -316,13 +316,13 @@ class AppImageManager(SoftwareManager): watcher.change_substatus('') return True - def uninstall(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool: + def uninstall(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher, disk_loader: DiskCacheLoader = None) -> TransactionResult: if os.path.exists(pkg.get_disk_cache_path()): handler = ProcessHandler(watcher) if not handler.handle(SystemProcess(new_subprocess(['rm', '-rf', pkg.get_disk_cache_path()]))): watcher.show_message(title=self.i18n['error'], body=self.i18n['appimage.uninstall.error.remove_folder'].format(bold(pkg.get_disk_cache_path()))) - return False + return TransactionResult.fail() de_path = self._gen_desktop_entry_path(pkg) if os.path.exists(de_path): @@ -330,7 +330,7 @@ class AppImageManager(SoftwareManager): self.revert_ignored_update(pkg) - return True + return TransactionResult(success=True, installed=None, removed=[pkg]) def get_managed_types(self) -> Set[Type[SoftwarePackage]]: return {AppImage} @@ -508,7 +508,6 @@ class AppImageManager(SoftwareManager): except: traceback.print_exc() - pkg.installed = True return TransactionResult(success=True, installed=[pkg], removed=[]) else: watcher.show_message(title=self.i18n['error'], diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index ad472e38..fa884ab9 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -997,9 +997,11 @@ class ArchManager(SoftwareManager): return True - def _uninstall(self, context: TransactionContext, remove_unneeded: bool = False): + def _uninstall(self, context: TransactionContext, remove_unneeded: bool = False, disk_loader: DiskCacheLoader = None): self._update_progress(context, 10) + net_available = internet.is_available() if disk_loader else True + required_by = self.deps_analyser.map_all_required_by({context.name}, set()) if required_by: @@ -1040,9 +1042,26 @@ class ArchManager(SoftwareManager): else: all_deps_map = None + if disk_loader and len(to_uninstall) > 1: # loading package instances in case the uninstall succeeds + instances = self.read_installed(disk_loader=disk_loader, + names={n for n in to_uninstall if n != context.name}, + internet_available=net_available).installed + + if len(instances) + 1 < len(to_uninstall): + self.logger.warning("Not all packages to be uninstalled could be read") + else: + instances = None + uninstalled = self._uninstall_pkgs(to_uninstall, context.root_password, context.handler) if uninstalled: + if disk_loader: # loading package instances in case the uninstall succeeds + context.removed[context.pkg.name] = context.pkg + + if instances: + for p in instances: + context.removed[p.name] = p + self._update_progress(context, 70) if all_deps_map: @@ -1076,10 +1095,22 @@ class ArchManager(SoftwareManager): all_unnecessary_to_uninstall = {*unnecessary_to_uninstall, *unnecessary_to_uninstall_deps} if not unnecessary_to_uninstall_deps or self._request_all_unncessary_uninstall_confirmation(all_unnecessary_to_uninstall, context): + + if disk_loader: # loading package instances in case the uninstall succeeds + unnecessary_instances = self.read_installed(disk_loader=disk_loader, + internet_available=net_available, + names=all_unnecessary_to_uninstall).installed + else: + unnecessary_instances = None + unneded_uninstalled = self._uninstall_pkgs(all_unnecessary_to_uninstall, context.root_password, context.handler) if unneded_uninstalled: to_uninstall.update(all_unnecessary_to_uninstall) + + if disk_loader and unnecessary_instances: # loading package instances in case the uninstall succeeds + for p in unnecessary_instances: + context.removed[p.name] = p else: self.logger.error("Could not uninstall some unnecessary packages") context.watcher.print("Could not uninstall some unnecessary packages") @@ -1102,20 +1133,28 @@ class ArchManager(SoftwareManager): self._update_progress(context, 100) return uninstalled - def uninstall(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatcher) -> bool: + def uninstall(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatcher, disk_loader: DiskCacheLoader) -> TransactionResult: self.aur_client.clean_caches() handler = ProcessHandler(watcher) if self._is_database_locked(handler, root_password): - return False + return TransactionResult.fail() - return self._uninstall(TransactionContext(name=pkg.name, - change_progress=True, - arch_config=read_config(), - watcher=watcher, - root_password=root_password, - handler=handler), - remove_unneeded=True) + removed = {} + success = self._uninstall(TransactionContext(name=pkg.name, + pkg=pkg, + change_progress=True, + arch_config=read_config(), + watcher=watcher, + root_password=root_password, + handler=handler, + removed=removed), + remove_unneeded=True, + disk_loader=disk_loader) # to be able to return all uninstalled packages + if success: + return TransactionResult(success=True, installed=None, removed=[*removed.values()] if removed else []) + else: + return TransactionResult.fail() def get_managed_types(self) -> Set["type"]: return {ArchPackage} @@ -1744,9 +1783,6 @@ class ArchManager(SoftwareManager): else: uninstalled = [p for p in pkgs_to_uninstall if p.name == conflict] if uninstalled: - uninstalled[0].icon_path = None - uninstalled[0].icon_url = None - uninstalled[0].installed = False context.removed[conflict] = uninstalled[0] else: self.logger.info("No conflict detected for '{}'".format(context.name)) @@ -1799,9 +1835,6 @@ class ArchManager(SoftwareManager): if installed_with_same_name: for p in installed_with_same_name: context.removed[p.name] = p - p.installed = False - p.icon_path = None - p.icon_url = None context.watcher.change_substatus(self.i18n['status.caching_data'].format(bold(context.name))) diff --git a/bauh/gems/flatpak/controller.py b/bauh/gems/flatpak/controller.py index 9ad15357..4ecf2c48 100644 --- a/bauh/gems/flatpak/controller.py +++ b/bauh/gems/flatpak/controller.py @@ -226,15 +226,17 @@ class FlatpakManager(SoftwareManager): watcher.change_substatus('') return True - def uninstall(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool: + def uninstall(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher, disk_loader: DiskCacheLoader) -> TransactionResult: uninstalled = ProcessHandler(watcher).handle(SystemProcess(subproc=flatpak.uninstall(pkg.ref, pkg.installation))) - if self.suggestions_cache: - self.suggestions_cache.delete(pkg.id) + if uninstalled: + if self.suggestions_cache: + self.suggestions_cache.delete(pkg.id) - self.revert_ignored_update(pkg) + self.revert_ignored_update(pkg) + return TransactionResult(success=True, installed=None, removed=[pkg]) - return uninstalled + return TransactionResult.fail() def get_info(self, app: FlatpakApplication) -> dict: if app.installed: @@ -354,7 +356,6 @@ class FlatpakManager(SoftwareManager): except: traceback.print_exc() - pkg.installed = res return TransactionResult(success=res, installed=[pkg] if res else [], removed=[]) def is_enabled(self): diff --git a/bauh/gems/snap/controller.py b/bauh/gems/snap/controller.py index 5ae91179..1140deeb 100644 --- a/bauh/gems/snap/controller.py +++ b/bauh/gems/snap/controller.py @@ -142,13 +142,16 @@ class SnapManager(SoftwareManager): def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher: ProcessWatcher) -> SystemProcess: raise Exception("'upgrade' is not supported by {}".format(SnapManager.__class__.__name__)) - def uninstall(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> bool: + def uninstall(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher, disk_loader: DiskCacheLoader) -> TransactionResult: uninstalled = ProcessHandler(watcher).handle(SystemProcess(subproc=snap.uninstall_and_stream(pkg.name, root_password))) - if self.suggestions_cache: - self.suggestions_cache.delete(pkg.name) + if uninstalled: + if self.suggestions_cache: + self.suggestions_cache.delete(pkg.name) - return uninstalled + return TransactionResult(success=True, installed=None, removed=[pkg]) + + return TransactionResult.fail() def get_managed_types(self) -> Set[Type[SoftwarePackage]]: return {SnapApplication} @@ -205,7 +208,6 @@ class SnapManager(SoftwareManager): if res and info_path: pkg.has_apps_field = snap.has_apps_field(pkg.name, info_path) - pkg.installed = res return TransactionResult(success=res, installed=[pkg] if res else [], removed=[]) else: self.logger.error("Could not find available channels in the installation output: {}".format(output)) @@ -213,7 +215,6 @@ class SnapManager(SoftwareManager): if info_path: pkg.has_apps_field = snap.has_apps_field(pkg.name, info_path) - pkg.installed = res return TransactionResult(success=res, installed=[pkg] if res else [], removed=[]) def is_enabled(self) -> bool: diff --git a/bauh/gems/web/controller.py b/bauh/gems/web/controller.py index 9bb83ee0..6dfcf86a 100644 --- a/bauh/gems/web/controller.py +++ b/bauh/gems/web/controller.py @@ -348,14 +348,14 @@ class WebApplicationManager(SoftwareManager): def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher: ProcessWatcher) -> bool: pass - def uninstall(self, pkg: WebApplication, root_password: str, watcher: ProcessWatcher) -> bool: + def uninstall(self, pkg: WebApplication, root_password: str, watcher: ProcessWatcher, disk_loader: DiskCacheLoader) -> TransactionResult: self.logger.info("Checking if {} installation directory {} exists".format(pkg.name, pkg.installation_dir)) if not os.path.exists(pkg.installation_dir): watcher.show_message(title=self.i18n['error'], body=self.i18n['web.uninstall.error.install_dir.not_found'].format(bold(pkg.installation_dir)), type_=MessageType.ERROR) - return False + return TransactionResult.fail() self.logger.info("Removing {} installation directory {}".format(pkg.name, pkg.installation_dir)) try: @@ -365,7 +365,7 @@ class WebApplicationManager(SoftwareManager): body=self.i18n['web.uninstall.error.remove'].format(bold(pkg.installation_dir)), type_=MessageType.ERROR) traceback.print_exc() - return False + return TransactionResult.fail() self.logger.info("Checking if {} desktop entry file {} exists".format(pkg.name, pkg.desktop_entry)) if os.path.exists(pkg.desktop_entry): @@ -412,7 +412,8 @@ class WebApplicationManager(SoftwareManager): watcher.show_message(title=self.i18n['error'], body=self.i18n['web.uninstall.error.remove'].format(bold(fix_path)), type_=MessageType.WARNING) - return True + + return TransactionResult(success=True, installed=None, removed=[pkg]) def get_managed_types(self) -> Set[Type[SoftwarePackage]]: return {WebApplication} @@ -763,7 +764,6 @@ class WebApplicationManager(SoftwareManager): if install_options: pkg.options_set = install_options - pkg.installed = True return TransactionResult(success=True, installed=[pkg], removed=[]) def _gen_desktop_entry_content(self, pkg: WebApplication) -> str: diff --git a/bauh/view/core/controller.py b/bauh/view/core/controller.py index 48c669cb..2bd8b2a7 100755 --- a/bauh/view/core/controller.py +++ b/bauh/view/core/controller.py @@ -285,20 +285,43 @@ class GenericSoftwareManager(SoftwareManager): return True - def uninstall(self, app: SoftwarePackage, root_password: str, handler: ProcessWatcher) -> bool: - man = self._get_manager_for(app) - - if man: - return man.uninstall(app, root_password, handler) - def _fill_post_transaction_status(self, pkg: SoftwarePackage, installed: bool): pkg.installed = installed - pkg.update = False if pkg.latest_version: pkg.version = pkg.latest_version + def _update_post_transaction_status(self, res: TransactionResult): + if res.success: + if res.installed: + for p in res.installed: + self._fill_post_transaction_status(p, True) + if res.removed: + for p in res.removed: + self._fill_post_transaction_status(p, False) + + def uninstall(self, pkg: SoftwarePackage, root_password: str, handler: ProcessWatcher, disk_loader: DiskCacheLoader = None) -> TransactionResult: + man = self._get_manager_for(pkg) + + if man: + ti = time.time() + disk_loader = self.disk_loader_factory.new() + disk_loader.start() + self.logger.info("Uninstalling {}".format(pkg.name)) + try: + res = man.uninstall(pkg, root_password, handler, disk_loader) + disk_loader.stop_working() + disk_loader.join() + self._update_post_transaction_status(res) + return res + except: + traceback.print_exc() + return TransactionResult(success=False, installed=[], removed=[]) + finally: + tf = time.time() + self.logger.info('Uninstallation of {}'.format(pkg) + 'took {0:.2f} minutes'.format((tf - ti) / 60)) + def install(self, app: SoftwarePackage, root_password: str, disk_loader: DiskCacheLoader, handler: ProcessWatcher) -> TransactionResult: man = self._get_manager_for(app) @@ -311,15 +334,7 @@ class GenericSoftwareManager(SoftwareManager): res = man.install(app, root_password, disk_loader, handler) disk_loader.stop_working() disk_loader.join() - - if res.success: - if res.installed: - for p in res.installed: - self._fill_post_transaction_status(p, True) - if res.removed: - for p in res.removed: - self._fill_post_transaction_status(p, False) - + self._update_post_transaction_status(res) return res except: traceback.print_exc() diff --git a/bauh/view/qt/apps_table.py b/bauh/view/qt/apps_table.py index 75b82c06..9838205e 100644 --- a/bauh/view/qt/apps_table.py +++ b/bauh/view/qt/apps_table.py @@ -194,11 +194,11 @@ class AppsTable(QTableWidget): self._update_row(pkg, change_update_col=change_update_col) - def _uninstall_app(self, app_v: PackageView): + def _uninstall(self, pkg: PackageView): if dialog.ask_confirmation(title=self.i18n['manage_window.apps_table.row.actions.uninstall.popup.title'], - body=self._parag(self.i18n['manage_window.apps_table.row.actions.uninstall.popup.body'].format(self._bold(str(app_v)))), + body=self._parag(self.i18n['manage_window.apps_table.row.actions.uninstall.popup.body'].format(self._bold(str(pkg)))), i18n=self.i18n): - self.window.uninstall_app(app_v) + self.window.uninstall_package(pkg) def _bold(self, text: str) -> str: return '{}'.format(text) @@ -284,7 +284,7 @@ class AppsTable(QTableWidget): if change_update_col: col_update = None - if update_check_enabled and not pkg.model.is_update_ignored() and pkg.model.update: + if update_check_enabled and pkg.model.installed and not pkg.model.is_update_ignored() and pkg.model.update: col_update = QToolBar() col_update.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) col_update.addWidget(UpdateToggleButton(pkg=pkg, @@ -323,7 +323,7 @@ class AppsTable(QTableWidget): if pkg.model.installed: if pkg.model.can_be_uninstalled(): def uninstall(): - self._uninstall_app(pkg) + self._uninstall(pkg) style = 'color: {c}; font-size: 10px; font-weight: bold;'.format(c=BROWN) item = self._gen_row_button(self.i18n['uninstall'].capitalize(), style, uninstall) @@ -408,7 +408,7 @@ class AppsTable(QTableWidget): item.setText(name) icon_path = pkg.model.get_disk_icon_path() - if pkg.model.supports_disk_cache() and icon_path: + if pkg.model.installed and pkg.model.supports_disk_cache() and icon_path: if icon_path.startswith('/') and os.path.isfile(icon_path): with open(icon_path, 'rb') as f: icon_bytes = f.read() diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py index 574261ce..74f61a43 100644 --- a/bauh/view/qt/thread.py +++ b/bauh/view/qt/thread.py @@ -496,34 +496,42 @@ class RefreshApps(AsyncAction): self.pkg_types = None -class UninstallApp(AsyncAction): +class UninstallPackage(AsyncAction): - def __init__(self, manager: SoftwareManager, icon_cache: MemoryCache, i18n: I18n, app: PackageView = None): - super(UninstallApp, self).__init__() - self.app = app + def __init__(self, manager: SoftwareManager, icon_cache: MemoryCache, i18n: I18n, pkg: PackageView = None): + super(UninstallPackage, self).__init__() + self.pkg = pkg self.manager = manager self.icon_cache = icon_cache self.root_pwd = None self.i18n = i18n def run(self): - if self.app: - if self.app.model.supports_backup(): + if self.pkg: + if self.pkg.model.supports_backup(): if not self.request_backup(read_config(), 'uninstall', self.i18n, self.root_pwd): self.notify_finished(False) - self.app = None + self.pkg = None self.root_pwd = None return - success = self.manager.uninstall(self.app.model, self.root_pwd, self) + try: + res = self.manager.uninstall(self.pkg.model, self.root_pwd, self, None) - if success: - self.icon_cache.delete(self.app.model.icon_url) - self.manager.clean_cache_for(self.app.model) + if res.success and res.removed: + for p in res.removed: + if p.icon_url: + self.icon_cache.delete(p.icon_url) - self.notify_finished(self.app if success else None) - self.app = None - self.root_pwd = None + self.manager.clean_cache_for(p) + + self.notify_finished({'success': res.success, 'removed': res.removed, 'pkg': self.pkg}) + except: + traceback.print_exc() + self.notify_finished({'success': False, 'removed': None, 'pkg': self.pkg}) + finally: + self.pkg = None + self.root_pwd = None class DowngradeApp(AsyncAction): diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py index 91e1c8c7..91dcc87e 100755 --- a/bauh/view/qt/window.py +++ b/bauh/view/qt/window.py @@ -32,7 +32,7 @@ from bauh.view.qt.info import InfoDialog from bauh.view.qt.root import ask_root_password from bauh.view.qt.screenshots import ScreenshotsDialog from bauh.view.qt.settings import SettingsWindow -from bauh.view.qt.thread import UpgradeSelected, RefreshApps, UninstallApp, DowngradeApp, GetAppInfo, \ +from bauh.view.qt.thread import UpgradeSelected, RefreshApps, UninstallPackage, DowngradeApp, GetAppInfo, \ GetAppHistory, SearchPackages, InstallPackage, AnimateProgress, NotifyPackagesReady, FindSuggestions, \ ListWarnings, \ AsyncAction, LaunchApp, ApplyFilters, CustomSoftwareAction, GetScreenshots, CustomAction, NotifyInstalledLoaded, \ @@ -303,7 +303,7 @@ class ManageWindow(QWidget): self.thread_update = self._bind_async_action(UpgradeSelected(self.manager, self.i18n), finished_call=self._finish_upgrade_selected) self.thread_refresh = self._bind_async_action(RefreshApps(self.manager), finished_call=self._finish_refresh_apps, only_finished=True) - self.thread_uninstall = self._bind_async_action(UninstallApp(self.manager, self.icon_cache, self.i18n), finished_call=self._finish_uninstall) + self.thread_uninstall = self._bind_async_action(UninstallPackage(self.manager, self.icon_cache, self.i18n), finished_call=self._finish_uninstall) self.thread_get_info = self._bind_async_action(GetAppInfo(self.manager), finished_call=self._finish_get_info) self.thread_get_history = self._bind_async_action(GetAppHistory(self.manager, self.i18n), finished_call=self._finish_get_history) self.thread_search = self._bind_async_action(SearchPackages(self.manager), finished_call=self._finish_search, only_finished=True) @@ -520,7 +520,7 @@ class ManageWindow(QWidget): self.ref_checkbox_only_apps.setVisible(True) self.input_search.setText('') self.input_name_filter.setText('') - self._begin_action(self.i18n['manage_window.status.installed'], keep_bt_installed=False, clear_filters=not self.recent_uninstall) + self._begin_action(self.i18n['manage_window.status.installed'], keep_bt_installed=False) self.thread_load_installed.start() def _finish_loading_installed(self): @@ -606,7 +606,7 @@ class ManageWindow(QWidget): self.ref_checkbox_updates.setVisible(False) self.ref_checkbox_only_apps.setVisible(False) - self._begin_action(self.i18n['manage_window.status.refreshing'], keep_bt_installed=False, clear_filters=not self.recent_uninstall) + self._begin_action(self.i18n['manage_window.status.refreshing'], keep_bt_installed=False) self.thread_refresh.pkg_types = pkg_types self.thread_refresh.start() @@ -616,7 +616,7 @@ class ManageWindow(QWidget): self._handle_console_option(False) self.ref_checkbox_updates.setVisible(False) self.ref_checkbox_only_apps.setVisible(False) - self._begin_action(self.i18n['manage_window.status.suggestions'], keep_bt_installed=False, clear_filters=not self.recent_uninstall) + self._begin_action(self.i18n['manage_window.status.suggestions'], keep_bt_installed=False) self.thread_suggestions.filter_installed = True self.thread_suggestions.start() @@ -626,21 +626,20 @@ class ManageWindow(QWidget): self.ref_checkbox_only_apps.setVisible(bool(res['installed'])) self.ref_bt_upgrade.setVisible(True) - self.update_pkgs(res['installed'], as_installed=as_installed, types=res['types'], keep_filters=self.recent_uninstall and res['types']) + self.update_pkgs(res['installed'], as_installed=as_installed, types=res['types']) self.load_suggestions = False - self.recent_uninstall = False self.types_changed = False - def uninstall_app(self, app: PackageView): - pwd, proceed = self._ask_root_password('uninstall', app) + def uninstall_package(self, pkg: PackageView): + pwd, proceed = self._ask_root_password('uninstall', pkg) if not proceed: return self._handle_console_option(True) - self._begin_action('{} {}'.format(self.i18n['manage_window.status.uninstalling'], app.model.name), clear_filters=False) + self._begin_action('{} {}'.format(self.i18n['manage_window.status.uninstalling'], pkg.model.name), clear_filters=False) - self.thread_uninstall.app = app + self.thread_uninstall.pkg = pkg self.thread_uninstall.root_pwd = pwd self.thread_uninstall.start() @@ -649,29 +648,64 @@ class ManageWindow(QWidget): self.thread_run_app.app = app self.thread_run_app.start() - def _finish_uninstall(self, pkgv: PackageView): - self.finish_action() + def _finish_uninstall(self, res: dict): + self.finish_action(keep_filters=True) - if pkgv: + if res['success']: + src_pkg = res['pkg'] if self._can_notify_user(): - util.notify_user('{} ({}) {}'.format(pkgv.model.name, pkgv.model.get_type(), self.i18n['uninstalled'])) + util.notify_user('{} ({}) {}'.format(src_pkg.model.name, src_pkg.model.get_type(), self.i18n['uninstalled'])) - if not self.search_performed: - only_pkg_type = len([p for p in self.pkgs if p.model.get_type() == pkgv.model.get_type()]) >= 2 - else: - only_pkg_type = False + if res['removed']: + for list_idx, pkg_list in enumerate((self.pkgs_available, self.pkgs, self.pkgs_installed)): + if pkg_list: + removed_idxs = [] + for pkgv_idx, pkgv in enumerate(pkg_list): + if len(removed_idxs) == len(res['removed']): + break + + for model in res['removed']: + if pkgv.model == model: + if list_idx == 0: # updates the model + pkgv.model = model + + if not self.search_performed or list_idx == 2: # always from the installed packages + removed_idxs.append(pkgv_idx) + + if self.search_performed and list_idx == 1: # only for displayed + self.table_apps.update_package(pkgv) + + break # as the model has been found, stops the loop + + if removed_idxs: + # updating the list + removed_idxs.sort() + for decrement, pkg_idx in enumerate(removed_idxs): + del pkg_list[pkg_idx - decrement] + + if list_idx == 1: # updates the rows if the current list reprents the displayed packages: + for decrement, idx in enumerate(removed_idxs): + self.table_apps.removeRow(idx - decrement) + + for new_idx, pkgv in enumerate(self.pkgs): # updating the package indexes + pkgv.table_index = new_idx - self.recent_uninstall = True - self.refresh_packages(pkg_types={pkgv.model.__class__} if only_pkg_type else None) self.update_custom_actions() notify_tray() else: if self._can_notify_user(): - util.notify_user('{}: {}'.format(pkgv.model.name, self.i18n['notification.uninstall.failed'])) + util.notify_user('{}: {}'.format(res['pkg'].model.name, self.i18n['notification.uninstall.failed'])) self.checkbox_console.setChecked(True) + for ref in (self.ref_combo_categories, self.ref_input_name_filter): + if not ref.isVisible(): + ref.setVisible(True) + + if self.combo_filter_type.count() > 2 and not self.ref_combo_filter_type.isVisible(): + self.ref_combo_filter_type.setVisible(True) + def _can_notify_user(self): return bool(self.config['system']['notifications']) and (self.isHidden() or self.isMinimized())