[improvement][ui] uninstallation does not refresh the table if it succeeds

This commit is contained in:
Vinicius Moreira
2020-06-16 11:41:06 -03:00
parent 81ddae5cbd
commit eee9e9abf8
11 changed files with 196 additions and 100 deletions

View File

@@ -9,7 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- AppImage - AppImage
- able to update AppImages with continuous releases - able to update AppImages with continuous releases
- UI - 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 - filters algorithm speed and sorting
- displaying wait cursor over some components - displaying wait cursor over some components

View File

@@ -70,6 +70,10 @@ class TransactionResult:
self.installed = installed self.installed = installed
self.removed = removed self.removed = removed
@staticmethod
def fail() -> "TransactionResult":
return TransactionResult(success=False, installed=None, removed=None)
class SoftwareManager(ABC): class SoftwareManager(ABC):
@@ -146,12 +150,13 @@ class SoftwareManager(ABC):
pass pass
@abstractmethod @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 pkg:
:param root_password: the root user password (if required) :param root_password: the root user password (if required)
:param watcher: :param watcher:
:return: if the uninstall succeeded :param disk_loader:
:return:
""" """
pass pass

View File

@@ -277,7 +277,7 @@ class AppImageManager(SoftwareManager):
type_=MessageType.ERROR) type_=MessageType.ERROR)
return False return False
else: 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] old_release = versions.history[versions.pkg_status_idx + 1]
pkg.version = old_release['0_version'] pkg.version = old_release['0_version']
pkg.latest_version = pkg.version pkg.latest_version = pkg.version
@@ -300,7 +300,7 @@ class AppImageManager(SoftwareManager):
for req in requirements.to_upgrade: for req in requirements.to_upgrade:
watcher.change_status("{} {} ({})...".format(self.i18n['manage_window.status.upgrading'], req.pkg.name, req.pkg.version)) 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'], watcher.show_message(title=self.i18n['error'],
body=self.i18n['appimage.error.uninstall_current_version'], body=self.i18n['appimage.error.uninstall_current_version'],
type_=MessageType.ERROR) type_=MessageType.ERROR)
@@ -316,13 +316,13 @@ class AppImageManager(SoftwareManager):
watcher.change_substatus('') watcher.change_substatus('')
return True 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()): if os.path.exists(pkg.get_disk_cache_path()):
handler = ProcessHandler(watcher) handler = ProcessHandler(watcher)
if not handler.handle(SystemProcess(new_subprocess(['rm', '-rf', pkg.get_disk_cache_path()]))): 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()))) 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) de_path = self._gen_desktop_entry_path(pkg)
if os.path.exists(de_path): if os.path.exists(de_path):
@@ -330,7 +330,7 @@ class AppImageManager(SoftwareManager):
self.revert_ignored_update(pkg) self.revert_ignored_update(pkg)
return True return TransactionResult(success=True, installed=None, removed=[pkg])
def get_managed_types(self) -> Set[Type[SoftwarePackage]]: def get_managed_types(self) -> Set[Type[SoftwarePackage]]:
return {AppImage} return {AppImage}
@@ -508,7 +508,6 @@ class AppImageManager(SoftwareManager):
except: except:
traceback.print_exc() traceback.print_exc()
pkg.installed = True
return TransactionResult(success=True, installed=[pkg], removed=[]) return TransactionResult(success=True, installed=[pkg], removed=[])
else: else:
watcher.show_message(title=self.i18n['error'], watcher.show_message(title=self.i18n['error'],

View File

@@ -997,9 +997,11 @@ class ArchManager(SoftwareManager):
return True 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) 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()) required_by = self.deps_analyser.map_all_required_by({context.name}, set())
if required_by: if required_by:
@@ -1040,9 +1042,26 @@ class ArchManager(SoftwareManager):
else: else:
all_deps_map = None 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) uninstalled = self._uninstall_pkgs(to_uninstall, context.root_password, context.handler)
if uninstalled: 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) self._update_progress(context, 70)
if all_deps_map: if all_deps_map:
@@ -1076,10 +1095,22 @@ class ArchManager(SoftwareManager):
all_unnecessary_to_uninstall = {*unnecessary_to_uninstall, *unnecessary_to_uninstall_deps} 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 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) unneded_uninstalled = self._uninstall_pkgs(all_unnecessary_to_uninstall, context.root_password, context.handler)
if unneded_uninstalled: if unneded_uninstalled:
to_uninstall.update(all_unnecessary_to_uninstall) 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: else:
self.logger.error("Could not uninstall some unnecessary packages") self.logger.error("Could not uninstall some unnecessary packages")
context.watcher.print("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) self._update_progress(context, 100)
return uninstalled 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() self.aur_client.clean_caches()
handler = ProcessHandler(watcher) handler = ProcessHandler(watcher)
if self._is_database_locked(handler, root_password): if self._is_database_locked(handler, root_password):
return False return TransactionResult.fail()
return self._uninstall(TransactionContext(name=pkg.name, removed = {}
change_progress=True, success = self._uninstall(TransactionContext(name=pkg.name,
arch_config=read_config(), pkg=pkg,
watcher=watcher, change_progress=True,
root_password=root_password, arch_config=read_config(),
handler=handler), watcher=watcher,
remove_unneeded=True) 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"]: def get_managed_types(self) -> Set["type"]:
return {ArchPackage} return {ArchPackage}
@@ -1744,9 +1783,6 @@ class ArchManager(SoftwareManager):
else: else:
uninstalled = [p for p in pkgs_to_uninstall if p.name == conflict] uninstalled = [p for p in pkgs_to_uninstall if p.name == conflict]
if uninstalled: if uninstalled:
uninstalled[0].icon_path = None
uninstalled[0].icon_url = None
uninstalled[0].installed = False
context.removed[conflict] = uninstalled[0] context.removed[conflict] = uninstalled[0]
else: else:
self.logger.info("No conflict detected for '{}'".format(context.name)) self.logger.info("No conflict detected for '{}'".format(context.name))
@@ -1799,9 +1835,6 @@ class ArchManager(SoftwareManager):
if installed_with_same_name: if installed_with_same_name:
for p in installed_with_same_name: for p in installed_with_same_name:
context.removed[p.name] = p 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))) context.watcher.change_substatus(self.i18n['status.caching_data'].format(bold(context.name)))

View File

@@ -226,15 +226,17 @@ class FlatpakManager(SoftwareManager):
watcher.change_substatus('') watcher.change_substatus('')
return True 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))) uninstalled = ProcessHandler(watcher).handle(SystemProcess(subproc=flatpak.uninstall(pkg.ref, pkg.installation)))
if self.suggestions_cache: if uninstalled:
self.suggestions_cache.delete(pkg.id) 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: def get_info(self, app: FlatpakApplication) -> dict:
if app.installed: if app.installed:
@@ -354,7 +356,6 @@ class FlatpakManager(SoftwareManager):
except: except:
traceback.print_exc() traceback.print_exc()
pkg.installed = res
return TransactionResult(success=res, installed=[pkg] if res else [], removed=[]) return TransactionResult(success=res, installed=[pkg] if res else [], removed=[])
def is_enabled(self): def is_enabled(self):

View File

@@ -142,13 +142,16 @@ class SnapManager(SoftwareManager):
def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher: ProcessWatcher) -> SystemProcess: def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher: ProcessWatcher) -> SystemProcess:
raise Exception("'upgrade' is not supported by {}".format(SnapManager.__class__.__name__)) 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))) uninstalled = ProcessHandler(watcher).handle(SystemProcess(subproc=snap.uninstall_and_stream(pkg.name, root_password)))
if self.suggestions_cache: if uninstalled:
self.suggestions_cache.delete(pkg.name) 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]]: def get_managed_types(self) -> Set[Type[SoftwarePackage]]:
return {SnapApplication} return {SnapApplication}
@@ -205,7 +208,6 @@ class SnapManager(SoftwareManager):
if res and info_path: if res and info_path:
pkg.has_apps_field = snap.has_apps_field(pkg.name, 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=[]) return TransactionResult(success=res, installed=[pkg] if res else [], removed=[])
else: else:
self.logger.error("Could not find available channels in the installation output: {}".format(output)) self.logger.error("Could not find available channels in the installation output: {}".format(output))
@@ -213,7 +215,6 @@ class SnapManager(SoftwareManager):
if info_path: if info_path:
pkg.has_apps_field = snap.has_apps_field(pkg.name, 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=[]) return TransactionResult(success=res, installed=[pkg] if res else [], removed=[])
def is_enabled(self) -> bool: def is_enabled(self) -> bool:

View File

@@ -348,14 +348,14 @@ class WebApplicationManager(SoftwareManager):
def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher: ProcessWatcher) -> bool: def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher: ProcessWatcher) -> bool:
pass 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)) self.logger.info("Checking if {} installation directory {} exists".format(pkg.name, pkg.installation_dir))
if not os.path.exists(pkg.installation_dir): if not os.path.exists(pkg.installation_dir):
watcher.show_message(title=self.i18n['error'], watcher.show_message(title=self.i18n['error'],
body=self.i18n['web.uninstall.error.install_dir.not_found'].format(bold(pkg.installation_dir)), body=self.i18n['web.uninstall.error.install_dir.not_found'].format(bold(pkg.installation_dir)),
type_=MessageType.ERROR) type_=MessageType.ERROR)
return False return TransactionResult.fail()
self.logger.info("Removing {} installation directory {}".format(pkg.name, pkg.installation_dir)) self.logger.info("Removing {} installation directory {}".format(pkg.name, pkg.installation_dir))
try: try:
@@ -365,7 +365,7 @@ class WebApplicationManager(SoftwareManager):
body=self.i18n['web.uninstall.error.remove'].format(bold(pkg.installation_dir)), body=self.i18n['web.uninstall.error.remove'].format(bold(pkg.installation_dir)),
type_=MessageType.ERROR) type_=MessageType.ERROR)
traceback.print_exc() traceback.print_exc()
return False return TransactionResult.fail()
self.logger.info("Checking if {} desktop entry file {} exists".format(pkg.name, pkg.desktop_entry)) self.logger.info("Checking if {} desktop entry file {} exists".format(pkg.name, pkg.desktop_entry))
if os.path.exists(pkg.desktop_entry): if os.path.exists(pkg.desktop_entry):
@@ -412,7 +412,8 @@ class WebApplicationManager(SoftwareManager):
watcher.show_message(title=self.i18n['error'], watcher.show_message(title=self.i18n['error'],
body=self.i18n['web.uninstall.error.remove'].format(bold(fix_path)), body=self.i18n['web.uninstall.error.remove'].format(bold(fix_path)),
type_=MessageType.WARNING) type_=MessageType.WARNING)
return True
return TransactionResult(success=True, installed=None, removed=[pkg])
def get_managed_types(self) -> Set[Type[SoftwarePackage]]: def get_managed_types(self) -> Set[Type[SoftwarePackage]]:
return {WebApplication} return {WebApplication}
@@ -763,7 +764,6 @@ class WebApplicationManager(SoftwareManager):
if install_options: if install_options:
pkg.options_set = install_options pkg.options_set = install_options
pkg.installed = True
return TransactionResult(success=True, installed=[pkg], removed=[]) return TransactionResult(success=True, installed=[pkg], removed=[])
def _gen_desktop_entry_content(self, pkg: WebApplication) -> str: def _gen_desktop_entry_content(self, pkg: WebApplication) -> str:

View File

@@ -285,20 +285,43 @@ class GenericSoftwareManager(SoftwareManager):
return True 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): def _fill_post_transaction_status(self, pkg: SoftwarePackage, installed: bool):
pkg.installed = installed pkg.installed = installed
pkg.update = False pkg.update = False
if pkg.latest_version: if pkg.latest_version:
pkg.version = 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: def install(self, app: SoftwarePackage, root_password: str, disk_loader: DiskCacheLoader, handler: ProcessWatcher) -> TransactionResult:
man = self._get_manager_for(app) man = self._get_manager_for(app)
@@ -311,15 +334,7 @@ class GenericSoftwareManager(SoftwareManager):
res = man.install(app, root_password, disk_loader, handler) res = man.install(app, root_password, disk_loader, handler)
disk_loader.stop_working() disk_loader.stop_working()
disk_loader.join() disk_loader.join()
self._update_post_transaction_status(res)
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)
return res return res
except: except:
traceback.print_exc() traceback.print_exc()

View File

@@ -194,11 +194,11 @@ class AppsTable(QTableWidget):
self._update_row(pkg, change_update_col=change_update_col) 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'], 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): i18n=self.i18n):
self.window.uninstall_app(app_v) self.window.uninstall_package(pkg)
def _bold(self, text: str) -> str: def _bold(self, text: str) -> str:
return '<span style="font-weight: bold">{}</span>'.format(text) return '<span style="font-weight: bold">{}</span>'.format(text)
@@ -284,7 +284,7 @@ class AppsTable(QTableWidget):
if change_update_col: if change_update_col:
col_update = None 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 = QToolBar()
col_update.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) col_update.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
col_update.addWidget(UpdateToggleButton(pkg=pkg, col_update.addWidget(UpdateToggleButton(pkg=pkg,
@@ -323,7 +323,7 @@ class AppsTable(QTableWidget):
if pkg.model.installed: if pkg.model.installed:
if pkg.model.can_be_uninstalled(): if pkg.model.can_be_uninstalled():
def uninstall(): def uninstall():
self._uninstall_app(pkg) self._uninstall(pkg)
style = 'color: {c}; font-size: 10px; font-weight: bold;'.format(c=BROWN) style = 'color: {c}; font-size: 10px; font-weight: bold;'.format(c=BROWN)
item = self._gen_row_button(self.i18n['uninstall'].capitalize(), style, uninstall) item = self._gen_row_button(self.i18n['uninstall'].capitalize(), style, uninstall)
@@ -408,7 +408,7 @@ class AppsTable(QTableWidget):
item.setText(name) item.setText(name)
icon_path = pkg.model.get_disk_icon_path() 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): if icon_path.startswith('/') and os.path.isfile(icon_path):
with open(icon_path, 'rb') as f: with open(icon_path, 'rb') as f:
icon_bytes = f.read() icon_bytes = f.read()

View File

@@ -496,34 +496,42 @@ class RefreshApps(AsyncAction):
self.pkg_types = None self.pkg_types = None
class UninstallApp(AsyncAction): class UninstallPackage(AsyncAction):
def __init__(self, manager: SoftwareManager, icon_cache: MemoryCache, i18n: I18n, app: PackageView = None): def __init__(self, manager: SoftwareManager, icon_cache: MemoryCache, i18n: I18n, pkg: PackageView = None):
super(UninstallApp, self).__init__() super(UninstallPackage, self).__init__()
self.app = app self.pkg = pkg
self.manager = manager self.manager = manager
self.icon_cache = icon_cache self.icon_cache = icon_cache
self.root_pwd = None self.root_pwd = None
self.i18n = i18n self.i18n = i18n
def run(self): def run(self):
if self.app: if self.pkg:
if self.app.model.supports_backup(): if self.pkg.model.supports_backup():
if not self.request_backup(read_config(), 'uninstall', self.i18n, self.root_pwd): if not self.request_backup(read_config(), 'uninstall', self.i18n, self.root_pwd):
self.notify_finished(False) self.notify_finished(False)
self.app = None self.pkg = None
self.root_pwd = None self.root_pwd = None
return 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: if res.success and res.removed:
self.icon_cache.delete(self.app.model.icon_url) for p in res.removed:
self.manager.clean_cache_for(self.app.model) if p.icon_url:
self.icon_cache.delete(p.icon_url)
self.notify_finished(self.app if success else None) self.manager.clean_cache_for(p)
self.app = None
self.root_pwd = None 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): class DowngradeApp(AsyncAction):

View File

@@ -32,7 +32,7 @@ from bauh.view.qt.info import InfoDialog
from bauh.view.qt.root import ask_root_password from bauh.view.qt.root import ask_root_password
from bauh.view.qt.screenshots import ScreenshotsDialog from bauh.view.qt.screenshots import ScreenshotsDialog
from bauh.view.qt.settings import SettingsWindow 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, \ GetAppHistory, SearchPackages, InstallPackage, AnimateProgress, NotifyPackagesReady, FindSuggestions, \
ListWarnings, \ ListWarnings, \
AsyncAction, LaunchApp, ApplyFilters, CustomSoftwareAction, GetScreenshots, CustomAction, NotifyInstalledLoaded, \ 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_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_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_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_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) 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.ref_checkbox_only_apps.setVisible(True)
self.input_search.setText('') self.input_search.setText('')
self.input_name_filter.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() self.thread_load_installed.start()
def _finish_loading_installed(self): def _finish_loading_installed(self):
@@ -606,7 +606,7 @@ class ManageWindow(QWidget):
self.ref_checkbox_updates.setVisible(False) self.ref_checkbox_updates.setVisible(False)
self.ref_checkbox_only_apps.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.pkg_types = pkg_types
self.thread_refresh.start() self.thread_refresh.start()
@@ -616,7 +616,7 @@ class ManageWindow(QWidget):
self._handle_console_option(False) self._handle_console_option(False)
self.ref_checkbox_updates.setVisible(False) self.ref_checkbox_updates.setVisible(False)
self.ref_checkbox_only_apps.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.filter_installed = True
self.thread_suggestions.start() self.thread_suggestions.start()
@@ -626,21 +626,20 @@ class ManageWindow(QWidget):
self.ref_checkbox_only_apps.setVisible(bool(res['installed'])) self.ref_checkbox_only_apps.setVisible(bool(res['installed']))
self.ref_bt_upgrade.setVisible(True) 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.load_suggestions = False
self.recent_uninstall = False
self.types_changed = False self.types_changed = False
def uninstall_app(self, app: PackageView): def uninstall_package(self, pkg: PackageView):
pwd, proceed = self._ask_root_password('uninstall', app) pwd, proceed = self._ask_root_password('uninstall', pkg)
if not proceed: if not proceed:
return return
self._handle_console_option(True) 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.root_pwd = pwd
self.thread_uninstall.start() self.thread_uninstall.start()
@@ -649,29 +648,64 @@ class ManageWindow(QWidget):
self.thread_run_app.app = app self.thread_run_app.app = app
self.thread_run_app.start() self.thread_run_app.start()
def _finish_uninstall(self, pkgv: PackageView): def _finish_uninstall(self, res: dict):
self.finish_action() self.finish_action(keep_filters=True)
if pkgv: if res['success']:
src_pkg = res['pkg']
if self._can_notify_user(): 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: if res['removed']:
only_pkg_type = len([p for p in self.pkgs if p.model.get_type() == pkgv.model.get_type()]) >= 2 for list_idx, pkg_list in enumerate((self.pkgs_available, self.pkgs, self.pkgs_installed)):
else: if pkg_list:
only_pkg_type = False 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() self.update_custom_actions()
notify_tray() notify_tray()
else: else:
if self._can_notify_user(): 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) 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): def _can_notify_user(self):
return bool(self.config['system']['notifications']) and (self.isHidden() or self.isMinimized()) return bool(self.config['system']['notifications']) and (self.isHidden() or self.isMinimized())