From a43e6404373ac39f20b11df3f9e820bf544a1299 Mon Sep 17 00:00:00 2001 From: Vinicius Moreira Date: Thu, 11 Jun 2020 16:51:55 -0300 Subject: [PATCH] [improvement][ui] not performing a full table refresh after installing a new package --- CHANGELOG.md | 3 ++ bauh/api/abstract/controller.py | 16 ++++++- bauh/gems/appimage/controller.py | 33 ++++++++------ bauh/gems/arch/controller.py | 78 ++++++++++++++++++++++++++++---- bauh/gems/arch/pacman.py | 34 +++++++------- bauh/gems/flatpak/controller.py | 13 +++--- bauh/gems/snap/controller.py | 11 +++-- bauh/gems/web/controller.py | 19 ++++---- bauh/view/core/controller.py | 12 +++-- bauh/view/qt/thread.py | 15 +++--- bauh/view/qt/window.py | 19 +++++++- 11 files changed, 183 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a2bef4e..7cae74dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,10 @@ 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 - filters algorithm speed and sorting - displaying wait cursor over some components + ### Fixes - AppImage - allowing some apps to be filled with empty category elements @@ -24,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - not calling initial required tasks after enabling a new package type on settings - minor fixes + ## [0.9.5] 2020-06-07 ### Features - new custom action (**+**) to open the system backups (snapshots). It is just a shortcut to Timeshift. diff --git a/bauh/api/abstract/controller.py b/bauh/api/abstract/controller.py index f3f7157c..be76b9e3 100644 --- a/bauh/api/abstract/controller.py +++ b/bauh/api/abstract/controller.py @@ -60,6 +60,17 @@ class UpgradeRequirements: self.cannot_upgrade = cannot_upgrade +class TransactionResult: + """ + The result of a given operation + """ + + def __init__(self, success: bool, installed: List[SoftwarePackage], removed: List[SoftwarePackage]): + self.success = success + self.installed = installed + self.removed = removed + + class SoftwareManager(ABC): """ @@ -169,12 +180,13 @@ class SoftwareManager(ABC): pass @abstractmethod - def install(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool: + def install(self, pkg: SoftwarePackage, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult: """ :param pkg: :param root_password: the root user password (if required) + :param disk_loader :param watcher: - :return: if the installation succeeded + :return: """ pass diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py index f0565562..968224a9 100644 --- a/bauh/gems/appimage/controller.py +++ b/bauh/gems/appimage/controller.py @@ -16,7 +16,8 @@ from typing import Set, Type, List, Tuple from colorama import Fore from bauh.api.abstract.context import ApplicationContext -from bauh.api.abstract.controller import SoftwareManager, SearchResult, UpgradeRequirements, UpgradeRequirement +from bauh.api.abstract.controller import SoftwareManager, SearchResult, UpgradeRequirements, UpgradeRequirement, \ + TransactionResult from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion, \ @@ -109,13 +110,13 @@ class AppImageManager(SoftwareManager): if inp_cat.get_selected() != cat_ops[0].value: appim.categories.append(inp_cat.get_selected()) - installed = self.install(root_password=root_password, pkg=appim, watcher=watcher) + res = self.install(root_password=root_password, pkg=appim, disk_loader=None, watcher=watcher).success - if installed: + if res: appim.installed = True self.cache_to_disk(appim, None, False) - return installed + return res def update_file(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher): file_chooser = FileChooserComponent(label=self.i18n['file'].capitalize(), allowed_extensions={'AppImage'}) @@ -281,7 +282,7 @@ class AppImageManager(SoftwareManager): pkg.version = old_release['0_version'] pkg.latest_version = pkg.version pkg.url_download = old_release['2_url_download'] - if self.install(pkg, root_password, watcher): + if self.install(pkg, root_password, None, watcher).success: self.cache_to_disk(pkg, None, False) return True else: @@ -306,7 +307,7 @@ class AppImageManager(SoftwareManager): watcher.change_substatus('') return False - if not self.install(req.pkg, root_password, watcher): + if not self.install(req.pkg, root_password, None, watcher).success: watcher.change_substatus('') return False @@ -408,7 +409,7 @@ class AppImageManager(SoftwareManager): if RE_ICON_ENDS_WITH.match(f): return f - def install(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool: + def install(self, pkg: AppImage, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult: handler = ProcessHandler(watcher) out_dir = INSTALLATION_PATH + pkg.name.lower() @@ -440,7 +441,8 @@ class AppImageManager(SoftwareManager): watcher.show_message(title=self.i18n['error'].capitalize(), body=self.i18n['appimage.install.imported.rename_error'].format(bold(pkg.local_file_path.split('/')[-1]), bold(output)), type_=MessageType.ERROR) - return False + + return TransactionResult(success=False, installed=[], removed=[]) else: appimage_url = pkg.url_download_latest_version if pkg.update else pkg.url_download @@ -468,14 +470,14 @@ class AppImageManager(SoftwareManager): body=self.i18n['appimage.install.appimagelauncher.error'].format(appimgl=bold('AppImageLauncher'), app=bold(pkg.name)), type_=MessageType.ERROR) handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) - return False + return TransactionResult(success=False, installed=[], removed=[]) except: watcher.show_message(title=self.i18n['error'], body=traceback.format_exc(), type_=MessageType.ERROR) traceback.print_exc() handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) - return False + return TransactionResult(success=False, installed=[], removed=[]) watcher.change_substatus(self.i18n['appimage.install.desktop_entry']) extracted_folder = '{}/{}'.format(out_dir, 'squashfs-root') @@ -501,8 +503,13 @@ class AppImageManager(SoftwareManager): with open(self._gen_desktop_entry_path(pkg), 'w+') as f: f.write(de_content) - shutil.rmtree(extracted_folder) - return True + try: + shutil.rmtree(extracted_folder) + except: + traceback.print_exc() + + pkg.installed = True + return TransactionResult(success=True, installed=[pkg], removed=[]) else: watcher.show_message(title=self.i18n['error'], body='Could extract content from {}'.format(bold(file_name)), @@ -513,7 +520,7 @@ class AppImageManager(SoftwareManager): type_=MessageType.ERROR) handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) - return False + return TransactionResult(success=False, installed=[], removed=[]) def _gen_desktop_entry_path(self, app: AppImage) -> str: return '{}/bauh_appimage_{}.desktop'.format(DESKTOP_ENTRIES_PATH, app.name.lower()) diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index fa4cea46..cfa3aa6b 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -15,7 +15,8 @@ from typing import List, Set, Type, Tuple, Dict, Iterable import requests -from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements +from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements, \ + TransactionResult from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.model import PackageUpdate, PackageHistory, SoftwarePackage, PackageSuggestion, PackageStatus, \ @@ -63,7 +64,8 @@ class TransactionContext: install_file: str = None, repository: str = None, pkg: ArchPackage = None, remote_repo_map: Dict[str, str] = None, provided_map: Dict[str, Set[str]] = None, remote_provided_map: Dict[str, Set[str]] = None, aur_idx: Set[str] = None, - missing_deps: List[Tuple[str, str]] = None): + missing_deps: List[Tuple[str, str]] = None, installed: Set[str] = None, removed: Dict[str, SoftwarePackage] = None, + disk_loader: DiskCacheLoader = None): self.name = name self.base = base self.maintainer = maintainer @@ -84,12 +86,16 @@ class TransactionContext: self.remote_provided_map = remote_provided_map self.aur_idx = aur_idx self.missing_deps = missing_deps + self.installed = installed + self.removed = removed + self.disk_loader = disk_loader @classmethod def gen_context_from(cls, pkg: ArchPackage, arch_config: dict, root_password: str, handler: ProcessHandler) -> "TransactionContext": return cls(name=pkg.name, base=pkg.get_base_name(), maintainer=pkg.maintainer, repository=pkg.repository, arch_config=arch_config, watcher=handler.watcher, handler=handler, skip_opt_deps=True, - change_progress=True, root_password=root_password, dependency=False) + change_progress=True, root_password=root_password, dependency=False, + installed=set(), removed={}) def get_base_name(self): return self.base if self.base else self.name @@ -99,7 +105,7 @@ class TransactionContext: def clone_base(self): return TransactionContext(watcher=self.watcher, handler=self.handler, root_password=self.root_password, - arch_config=self.config) + arch_config=self.config, installed=set(), removed={}) def gen_dep_context(self, name: str, repository: str): dep_context = self.clone_base() @@ -107,6 +113,8 @@ class TransactionContext: dep_context.repository = repository dep_context.dependency = True dep_context.change_progress = False + dep_context.installed = set() + dep_context.removed = {} return dep_context def has_install_file(self) -> bool: @@ -894,7 +902,7 @@ class ArchManager(SoftwareManager): context.change_progress = False try: - if not self.install(pkg=pkg, root_password=root_password, watcher=watcher, context=context): + if not self.install(pkg=pkg, root_password=root_password, watcher=watcher, disk_loader=None, context=context).success: watcher.print(self.i18n['arch.upgrade.fail'].format('"{}"'.format(pkg.name))) self.logger.error("Could not upgrade AUR package '{}'".format(pkg.name)) watcher.change_substatus('') @@ -1500,6 +1508,8 @@ class ArchManager(SoftwareManager): message.show_deps_not_installed(context.watcher, context.name, deps_not_installed, self.i18n) return False + context.installed.update({d[0] for d in missing_deps}) + return True def _list_missing_deps(self, context: TransactionContext) -> List[Tuple[str, str]]: @@ -1657,6 +1667,8 @@ class ArchManager(SoftwareManager): if deps_not_installed: message.show_optdeps_not_installed(deps_not_installed, context.watcher, self.i18n) + else: + context.installed.update({dep[0] for dep in sorted_deps}) return True @@ -1703,6 +1715,12 @@ class ArchManager(SoftwareManager): to_uninstall = [conflict for conflict in conflicting_apps if conflict != context.name] self.logger.info("Preparing to uninstall conflicting packages: {}".format(to_uninstall)) + + pkgs_to_uninstall = self.read_installed(disk_loader=context.disk_loader, names=to_uninstall, internet_available=True).installed + + if not pkgs_to_uninstall: + self.logger.warning("Could not load packages to uninstall") + for conflict in to_uninstall: context.watcher.change_substatus(self.i18n['arch.uninstalling.conflict'].format(bold(conflict))) @@ -1711,6 +1729,13 @@ class ArchManager(SoftwareManager): body=self.i18n['arch.uninstalling.conflict.fail'].format(bold(conflict)), type_=MessageType.ERROR) return False + 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)) @@ -1742,6 +1767,7 @@ class ArchManager(SoftwareManager): else: status_handler = None + installed_with_same_name = self.read_installed(disk_loader=context.disk_loader, internet_available=True, names={context.name}).installed context.watcher.change_substatus(self.i18n['arch.installing.package'].format(bold(context.name))) installed, _ = context.handler.handle_simple(pacman.install_as_process(pkgpaths=to_install, root_password=context.root_password, @@ -1755,6 +1781,16 @@ class ArchManager(SoftwareManager): self._update_progress(context, 95) if installed: + context.installed.add(context.name) + context.installed.update((p for p in to_install if not p.startswith('/'))) + + 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))) if not context.maintainer: @@ -1887,16 +1923,16 @@ class ArchManager(SoftwareManager): watcher.change_substatus(self.i18n['arch.makepkg.optimizing']) ArchCompilationOptimizer(arch_config, self.i18n, self.context.logger).optimize() - def install(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatcher, context: TransactionContext = None) -> bool: + def install(self, pkg: ArchPackage, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher, context: TransactionContext = None) -> TransactionResult: self.aur_client.clean_caches() if not self._check_action_allowed(pkg, watcher): - return False + return TransactionResult(success=False, installed=[], removed=[]) handler = ProcessHandler(watcher) if not context else context.handler if self._is_database_locked(handler, root_password): - return False + return TransactionResult(success=False, installed=[], removed=[]) if context: install_context = context @@ -1904,6 +1940,7 @@ class ArchManager(SoftwareManager): install_context = TransactionContext.gen_context_from(pkg=pkg, handler=handler, arch_config=read_config(), root_password=root_password) install_context.skip_opt_deps = False + install_context.disk_loader = disk_loader self._sync_databases(arch_config=install_context.config, root_password=root_password, handler=handler) @@ -1919,7 +1956,30 @@ class ArchManager(SoftwareManager): data = json.loads(data) pkg.fill_cached_data(data) - return res + installed = [] + + if res and disk_loader and install_context.installed: + installed.append(pkg) + + installed_to_load = [] + + if len(install_context.installed) > 1: + installed_to_load.extend({i for i in install_context.installed if i != pkg.name}) + + if installed_to_load: + installed_loaded = self.read_installed(disk_loader=disk_loader, + names=installed_to_load, + internet_available=True).installed + + installed.extend(installed_loaded) + + if len(installed_loaded) + 1 != len(install_context.installed): + missing = ','.join({p for p in installed_loaded if p.name not in install_context.installed}) + self.logger.warning("Could not load all installed packages. Missing: {}".format(missing)) + + removed = [*install_context.removed.values()] if install_context.removed else [] + + return TransactionResult(success=res, installed=installed, removed=removed) def _install_from_repository(self, context: TransactionContext) -> bool: try: diff --git a/bauh/gems/arch/pacman.py b/bauh/gems/arch/pacman.py index 8f23b66a..903d605e 100644 --- a/bauh/gems/arch/pacman.py +++ b/bauh/gems/arch/pacman.py @@ -100,26 +100,28 @@ def map_installed(names: Iterable[str] = None) -> dict: # returns a dict with w thread_ignored = Thread(target=_fill_ignored, args=(ignored,), daemon=True) thread_ignored.start() - allinfo = run_cmd('pacman -Qi{}'.format(' ' + ' '.join(names) if names else '')) + allinfo = run_cmd('pacman -Qi{}'.format(' ' + ' '.join(names) if names else ''), print_error=False) pkgs = {'signed': {}, 'not_signed': {}} current_pkg = {} - for idx, field_tuple in enumerate(RE_INSTALLED_FIELDS.findall(allinfo)): - if field_tuple[0].startswith('N'): - current_pkg['name'] = field_tuple[1].strip() - elif field_tuple[0].startswith('Ve'): - current_pkg['version'] = field_tuple[1].split(':')[-1].strip() - elif field_tuple[0].startswith('D'): - current_pkg['description'] = field_tuple[1].strip() - elif field_tuple[0].startswith('Va'): - if field_tuple[1].strip().lower() == 'none': - pkgs['not_signed'][current_pkg['name']] = current_pkg - del current_pkg['name'] - else: - pkgs['signed'][current_pkg['name']] = current_pkg - del current_pkg['name'] - current_pkg = {} + if allinfo: + for idx, field_tuple in enumerate(RE_INSTALLED_FIELDS.findall(allinfo)): + if field_tuple[0].startswith('N'): + current_pkg['name'] = field_tuple[1].strip() + elif field_tuple[0].startswith('Ve'): + current_pkg['version'] = field_tuple[1].split(':')[-1].strip() + elif field_tuple[0].startswith('D'): + current_pkg['description'] = field_tuple[1].strip() + elif field_tuple[0].startswith('Va'): + if field_tuple[1].strip().lower() == 'none': + pkgs['not_signed'][current_pkg['name']] = current_pkg + del current_pkg['name'] + else: + pkgs['signed'][current_pkg['name']] = current_pkg + del current_pkg['name'] + + current_pkg = {} if pkgs['signed'] or pkgs['not_signed']: thread_ignored.join() diff --git a/bauh/gems/flatpak/controller.py b/bauh/gems/flatpak/controller.py index 37f54b63..9ad15357 100644 --- a/bauh/gems/flatpak/controller.py +++ b/bauh/gems/flatpak/controller.py @@ -7,7 +7,7 @@ from threading import Thread from typing import List, Set, Type, Tuple from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements, \ - UpgradeRequirement + UpgradeRequirement, TransactionResult from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.model import PackageHistory, PackageUpdate, SoftwarePackage, PackageSuggestion, \ @@ -296,7 +296,7 @@ class FlatpakManager(SoftwareManager): return PackageHistory(pkg=pkg, history=commits, pkg_status_idx=status_idx) - def install(self, pkg: FlatpakApplication, root_password: str, watcher: ProcessWatcher) -> bool: + def install(self, pkg: FlatpakApplication, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult: config = read_config() @@ -310,7 +310,7 @@ class FlatpakManager(SoftwareManager): body=self.i18n['flatpak.install.bad_install_level.body'].format(field=bold('installation_level'), file=bold(CONFIG_FILE)), type_=MessageType.ERROR) - return False + return TransactionResult(success=False, installed=[], removed=[]) pkg.installation = install_level else: @@ -333,14 +333,14 @@ class FlatpakManager(SoftwareManager): user_password, valid = watcher.request_root_password() if not valid: watcher.print('Operation aborted') - return False + return TransactionResult(success=False, installed=[], removed=[]) else: if not handler.handle_simple(flatpak.set_default_remotes('system', user_password)): watcher.show_message(title=self.i18n['error'].capitalize(), body=self.i18n['flatpak.remotes.system_flathub.error'], type_=MessageType.ERROR) watcher.print("Operation cancelled") - return False + return TransactionResult(success=False, installed=[], removed=[]) res = handler.handle(SystemProcess(subproc=flatpak.install(str(pkg.id), pkg.origin, pkg.installation), wrong_error_phrase='Warning')) @@ -354,7 +354,8 @@ class FlatpakManager(SoftwareManager): except: traceback.print_exc() - return res + pkg.installed = res + return TransactionResult(success=res, installed=[pkg] if res else [], removed=[]) def is_enabled(self): return self.enabled diff --git a/bauh/gems/snap/controller.py b/bauh/gems/snap/controller.py index 5def1d6a..5ae91179 100644 --- a/bauh/gems/snap/controller.py +++ b/bauh/gems/snap/controller.py @@ -4,7 +4,8 @@ from datetime import datetime from threading import Thread from typing import List, Set, Type -from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext, UpgradeRequirements +from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext, UpgradeRequirements, \ + TransactionResult from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion, \ @@ -174,7 +175,7 @@ class SnapManager(SoftwareManager): def get_history(self, pkg: SnapApplication) -> PackageHistory: raise Exception("'get_history' is not supported by {}".format(pkg.__class__.__name__)) - def install(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> bool: + def install(self, pkg: SnapApplication, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult: info_path = self.get_info_path() if not info_path: @@ -204,14 +205,16 @@ class SnapManager(SoftwareManager): if res and info_path: pkg.has_apps_field = snap.has_apps_field(pkg.name, info_path) - return res + 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)) else: if info_path: pkg.has_apps_field = snap.has_apps_field(pkg.name, info_path) - return res + pkg.installed = res + return TransactionResult(success=res, installed=[pkg] if res else [], removed=[]) def is_enabled(self) -> bool: return self.enabled diff --git a/bauh/gems/web/controller.py b/bauh/gems/web/controller.py index 925f8980..9bb83ee0 100644 --- a/bauh/gems/web/controller.py +++ b/bauh/gems/web/controller.py @@ -17,7 +17,7 @@ from colorama import Fore from requests import exceptions, Response from bauh.api.abstract.context import ApplicationContext -from bauh.api.abstract.controller import SoftwareManager, SearchResult, UpgradeRequirements +from bauh.api.abstract.controller import SoftwareManager, SearchResult, UpgradeRequirements, TransactionResult from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.model import SoftwarePackage, CustomSoftwareAction, PackageSuggestion, PackageUpdate, \ @@ -617,13 +617,13 @@ class WebApplicationManager(SoftwareManager): pkg.name)) traceback.print_exc() - def install(self, pkg: WebApplication, root_password: str, watcher: ProcessWatcher) -> bool: + def install(self, pkg: WebApplication, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult: continue_install, install_options = self._ask_install_options(pkg, watcher) if not continue_install: watcher.print("Installation aborted by the user") - return False + return TransactionResult(success=False, installed=[], removed=[]) watcher.change_substatus(self.i18n['web.env.checking']) handler = ProcessHandler(watcher) @@ -635,18 +635,18 @@ class WebApplicationManager(SoftwareManager): watcher.show_message(title=self.i18n['error'].capitalize(), body=self.i18n['web.install.global_nativefier.unavailable'].format(n=bold('Nativefier'), app=bold(pkg.name)) + '.', type_=MessageType.ERROR) - return False + return TransactionResult(success=False, installed=[], removed=[]) env_components = self.env_updater.check_environment(app=pkg, local_config=local_config, env=env_settings, is_x86_x64_arch=self.context.is_system_x86_64()) comps_to_update = [c for c in env_components if c.update] if comps_to_update and not self._ask_update_permission(comps_to_update, watcher): - return False + return TransactionResult(success=False, installed=[], removed=[]) if not self.env_updater.update(components=comps_to_update, handler=handler): watcher.show_message(title=self.i18n['error'], body=self.i18n['web.env.error'].format(bold(pkg.name)), type_=MessageType.ERROR) - return False + return TransactionResult(success=False, installed=[], removed=[]) Path(INSTALLED_PATH).mkdir(parents=True, exist_ok=True) @@ -699,7 +699,7 @@ class WebApplicationManager(SoftwareManager): msg = '{}.{}.'.format(self.i18n['wen.install.error'].format(bold(pkg.name)), self.i18n['web.install.nativefier.error.unknown'].format(bold(self.i18n['details'].capitalize()))) watcher.show_message(title=self.i18n['error'], body=msg, type_=MessageType.ERROR) - return False + return TransactionResult(success=False, installed=[], removed=[]) inner_dir = os.listdir(app_dir) @@ -707,7 +707,7 @@ class WebApplicationManager(SoftwareManager): msg = '{}.{}.'.format(self.i18n['wen.install.error'].format(bold(pkg.name)), self.i18n['web.install.nativefier.error.inner_dir'].format(bold(app_dir))) watcher.show_message(title=self.i18n['error'], body=msg, type_=MessageType.ERROR) - return False + return TransactionResult(success=False, installed=[], removed=[]) # bringing the inner app folder to the 'installed' folder level: inner_dir = '{}/{}'.format(app_dir, inner_dir[0]) @@ -763,7 +763,8 @@ class WebApplicationManager(SoftwareManager): if install_options: pkg.options_set = install_options - return True + pkg.installed = True + return TransactionResult(success=True, installed=[pkg], removed=[]) def _gen_desktop_entry_content(self, pkg: WebApplication) -> str: return """ diff --git a/bauh/view/core/controller.py b/bauh/view/core/controller.py index 62bca641..6165357a 100755 --- a/bauh/view/core/controller.py +++ b/bauh/view/core/controller.py @@ -7,7 +7,7 @@ from threading import Thread from typing import List, Set, Type, Tuple, Dict from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext, UpgradeRequirements, \ - UpgradeRequirement + UpgradeRequirement, TransactionResult from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.model import SoftwarePackage, PackageUpdate, PackageHistory, PackageSuggestion, \ @@ -291,18 +291,22 @@ class GenericSoftwareManager(SoftwareManager): if man: return man.uninstall(app, root_password, handler) - def install(self, app: SoftwarePackage, root_password: str, handler: ProcessWatcher) -> bool: + def install(self, app: SoftwarePackage, root_password: str, disk_loader: DiskCacheLoader, handler: ProcessWatcher) -> TransactionResult: man = self._get_manager_for(app) if man: ti = time.time() + disk_loader = self.disk_loader_factory.new() + disk_loader.start() try: self.logger.info('Installing {}'.format(app)) - return man.install(app, root_password, handler) + return man.install(app, root_password, disk_loader, handler) except: traceback.print_exc() - return False + return TransactionResult(success=False, installed=[], removed=[]) finally: + disk_loader.stop_working() + disk_loader.join() tf = time.time() self.logger.info('Installation of {}'.format(app) + 'took {0:.2f} minutes'.format((tf - ti)/60)) diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py index a72ca182..5edd75b3 100644 --- a/bauh/view/qt/thread.py +++ b/bauh/view/qt/thread.py @@ -631,17 +631,20 @@ class InstallPackage(AsyncAction): def run(self): if self.pkg: - success = False + res = {'success': False, 'installed': None, 'removed': None, 'pkg': self.pkg} if self.pkg.model.supports_backup(): if not self.request_backup(read_config(), 'install', self.i18n, self.root_pwd): - self.signal_finished.emit({'success': False, 'pkg': self.pkg}) + self.signal_finished.emit(res) return try: - success = self.manager.install(self.pkg.model, self.root_pwd, self) + transaction_res = self.manager.install(self.pkg.model, self.root_pwd, None, self) + res['success'] = transaction_res.success + res['installed'] = transaction_res.installed + res['removed'] = transaction_res.removed - if success: + if transaction_res.success: self.pkg.model.installed = True if self.pkg.model.supports_disk_cache(): @@ -650,10 +653,10 @@ class InstallPackage(AsyncAction): icon_bytes=icon_data.get('bytes') if icon_data else None, only_icon=False) except (requests.exceptions.ConnectionError, NoInternetException): - success = False + res['success'] = False self.print(self.i18n['internet.required']) finally: - self.signal_finished.emit({'success': success, 'pkg': self.pkg}) + self.signal_finished.emit(res) self.pkg = None diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py index 41368e9a..484ddc44 100755 --- a/bauh/view/qt/window.py +++ b/bauh/view/qt/window.py @@ -1211,7 +1211,23 @@ class ManageWindow(QWidget): if self._can_notify_user(): util.notify_user(msg='{} ({}) {}'.format(res['pkg'].model.name, res['pkg'].model.get_type(), self.i18n['installed'])) - self._finish_refresh_apps({'installed': [res['pkg'].model], 'total': 1, 'types': None}) + models_updated = [] + + for key in ('installed', 'removed'): + if res.get(key): + models_updated.extend(res[key]) + + if models_updated: + view_to_update = [] + for displayed in self.pkgs: + for p in models_updated: + if displayed.model == p: + displayed.model = p + view_to_update.append(displayed) + + for pkgv in view_to_update: + self.table_apps.update_package(pkgv) + self.ref_bt_installed.setVisible(False) self.ref_checkbox_only_apps.setVisible(False) self.update_custom_actions() @@ -1324,3 +1340,4 @@ class ManageWindow(QWidget): body=self.i18n['action.{}.fail'.format(res['action'])].format(bold(res['pkg'].model.name)), type_=MessageType.ERROR) +