[improvement][ui] not performing a full table refresh after installing a new package

This commit is contained in:
Vinicius Moreira
2020-06-11 16:51:55 -03:00
parent cc5c89a350
commit a43e640437
11 changed files with 183 additions and 70 deletions

View File

@@ -9,8 +9,10 @@ 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
- filters algorithm speed and sorting - filters algorithm speed and sorting
- displaying wait cursor over some components - displaying wait cursor over some components
### Fixes ### Fixes
- AppImage - AppImage
- allowing some apps to be filled with empty category elements - 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 - not calling initial required tasks after enabling a new package type on settings
- minor fixes - minor fixes
## [0.9.5] 2020-06-07 ## [0.9.5] 2020-06-07
### Features ### Features
- new custom action (**+**) to open the system backups (snapshots). It is just a shortcut to Timeshift. - new custom action (**+**) to open the system backups (snapshots). It is just a shortcut to Timeshift.

View File

@@ -60,6 +60,17 @@ class UpgradeRequirements:
self.cannot_upgrade = cannot_upgrade 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): class SoftwareManager(ABC):
""" """
@@ -169,12 +180,13 @@ class SoftwareManager(ABC):
pass pass
@abstractmethod @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 pkg:
:param root_password: the root user password (if required) :param root_password: the root user password (if required)
:param disk_loader
:param watcher: :param watcher:
:return: if the installation succeeded :return:
""" """
pass pass

View File

@@ -16,7 +16,8 @@ from typing import Set, Type, List, Tuple
from colorama import Fore from colorama import Fore
from bauh.api.abstract.context import ApplicationContext 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.disk import DiskCacheLoader
from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.handler import ProcessWatcher, TaskManager
from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion, \ 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: if inp_cat.get_selected() != cat_ops[0].value:
appim.categories.append(inp_cat.get_selected()) 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 appim.installed = True
self.cache_to_disk(appim, None, False) self.cache_to_disk(appim, None, False)
return installed return res
def update_file(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher): def update_file(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher):
file_chooser = FileChooserComponent(label=self.i18n['file'].capitalize(), allowed_extensions={'AppImage'}) 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.version = old_release['0_version']
pkg.latest_version = pkg.version pkg.latest_version = pkg.version
pkg.url_download = old_release['2_url_download'] 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) self.cache_to_disk(pkg, None, False)
return True return True
else: else:
@@ -306,7 +307,7 @@ class AppImageManager(SoftwareManager):
watcher.change_substatus('') watcher.change_substatus('')
return False 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('') watcher.change_substatus('')
return False return False
@@ -408,7 +409,7 @@ class AppImageManager(SoftwareManager):
if RE_ICON_ENDS_WITH.match(f): if RE_ICON_ENDS_WITH.match(f):
return 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) handler = ProcessHandler(watcher)
out_dir = INSTALLATION_PATH + pkg.name.lower() out_dir = INSTALLATION_PATH + pkg.name.lower()
@@ -440,7 +441,8 @@ class AppImageManager(SoftwareManager):
watcher.show_message(title=self.i18n['error'].capitalize(), 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)), body=self.i18n['appimage.install.imported.rename_error'].format(bold(pkg.local_file_path.split('/')[-1]), bold(output)),
type_=MessageType.ERROR) type_=MessageType.ERROR)
return False
return TransactionResult(success=False, installed=[], removed=[])
else: else:
appimage_url = pkg.url_download_latest_version if pkg.update else pkg.url_download 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)), body=self.i18n['appimage.install.appimagelauncher.error'].format(appimgl=bold('AppImageLauncher'), app=bold(pkg.name)),
type_=MessageType.ERROR) type_=MessageType.ERROR)
handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir])))
return False return TransactionResult(success=False, installed=[], removed=[])
except: except:
watcher.show_message(title=self.i18n['error'], watcher.show_message(title=self.i18n['error'],
body=traceback.format_exc(), body=traceback.format_exc(),
type_=MessageType.ERROR) type_=MessageType.ERROR)
traceback.print_exc() traceback.print_exc()
handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) 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']) watcher.change_substatus(self.i18n['appimage.install.desktop_entry'])
extracted_folder = '{}/{}'.format(out_dir, 'squashfs-root') 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: with open(self._gen_desktop_entry_path(pkg), 'w+') as f:
f.write(de_content) f.write(de_content)
shutil.rmtree(extracted_folder) try:
return True shutil.rmtree(extracted_folder)
except:
traceback.print_exc()
pkg.installed = True
return TransactionResult(success=True, installed=[pkg], removed=[])
else: else:
watcher.show_message(title=self.i18n['error'], watcher.show_message(title=self.i18n['error'],
body='Could extract content from {}'.format(bold(file_name)), body='Could extract content from {}'.format(bold(file_name)),
@@ -513,7 +520,7 @@ class AppImageManager(SoftwareManager):
type_=MessageType.ERROR) type_=MessageType.ERROR)
handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) 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: def _gen_desktop_entry_path(self, app: AppImage) -> str:
return '{}/bauh_appimage_{}.desktop'.format(DESKTOP_ENTRIES_PATH, app.name.lower()) return '{}/bauh_appimage_{}.desktop'.format(DESKTOP_ENTRIES_PATH, app.name.lower())

View File

@@ -15,7 +15,8 @@ from typing import List, Set, Type, Tuple, Dict, Iterable
import requests 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.disk import DiskCacheLoader
from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.handler import ProcessWatcher, TaskManager
from bauh.api.abstract.model import PackageUpdate, PackageHistory, SoftwarePackage, PackageSuggestion, PackageStatus, \ 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, 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_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, 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.name = name
self.base = base self.base = base
self.maintainer = maintainer self.maintainer = maintainer
@@ -84,12 +86,16 @@ class TransactionContext:
self.remote_provided_map = remote_provided_map self.remote_provided_map = remote_provided_map
self.aur_idx = aur_idx self.aur_idx = aur_idx
self.missing_deps = missing_deps self.missing_deps = missing_deps
self.installed = installed
self.removed = removed
self.disk_loader = disk_loader
@classmethod @classmethod
def gen_context_from(cls, pkg: ArchPackage, arch_config: dict, root_password: str, handler: ProcessHandler) -> "TransactionContext": 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, 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, 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): def get_base_name(self):
return self.base if self.base else self.name return self.base if self.base else self.name
@@ -99,7 +105,7 @@ class TransactionContext:
def clone_base(self): def clone_base(self):
return TransactionContext(watcher=self.watcher, handler=self.handler, root_password=self.root_password, 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): def gen_dep_context(self, name: str, repository: str):
dep_context = self.clone_base() dep_context = self.clone_base()
@@ -107,6 +113,8 @@ class TransactionContext:
dep_context.repository = repository dep_context.repository = repository
dep_context.dependency = True dep_context.dependency = True
dep_context.change_progress = False dep_context.change_progress = False
dep_context.installed = set()
dep_context.removed = {}
return dep_context return dep_context
def has_install_file(self) -> bool: def has_install_file(self) -> bool:
@@ -894,7 +902,7 @@ class ArchManager(SoftwareManager):
context.change_progress = False context.change_progress = False
try: 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))) watcher.print(self.i18n['arch.upgrade.fail'].format('"{}"'.format(pkg.name)))
self.logger.error("Could not upgrade AUR package '{}'".format(pkg.name)) self.logger.error("Could not upgrade AUR package '{}'".format(pkg.name))
watcher.change_substatus('') watcher.change_substatus('')
@@ -1500,6 +1508,8 @@ class ArchManager(SoftwareManager):
message.show_deps_not_installed(context.watcher, context.name, deps_not_installed, self.i18n) message.show_deps_not_installed(context.watcher, context.name, deps_not_installed, self.i18n)
return False return False
context.installed.update({d[0] for d in missing_deps})
return True return True
def _list_missing_deps(self, context: TransactionContext) -> List[Tuple[str, str]]: def _list_missing_deps(self, context: TransactionContext) -> List[Tuple[str, str]]:
@@ -1657,6 +1667,8 @@ class ArchManager(SoftwareManager):
if deps_not_installed: if deps_not_installed:
message.show_optdeps_not_installed(deps_not_installed, context.watcher, self.i18n) 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 return True
@@ -1703,6 +1715,12 @@ class ArchManager(SoftwareManager):
to_uninstall = [conflict for conflict in conflicting_apps if conflict != context.name] to_uninstall = [conflict for conflict in conflicting_apps if conflict != context.name]
self.logger.info("Preparing to uninstall conflicting packages: {}".format(to_uninstall)) 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: for conflict in to_uninstall:
context.watcher.change_substatus(self.i18n['arch.uninstalling.conflict'].format(bold(conflict))) 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)), body=self.i18n['arch.uninstalling.conflict.fail'].format(bold(conflict)),
type_=MessageType.ERROR) type_=MessageType.ERROR)
return False 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: else:
self.logger.info("No conflict detected for '{}'".format(context.name)) self.logger.info("No conflict detected for '{}'".format(context.name))
@@ -1742,6 +1767,7 @@ class ArchManager(SoftwareManager):
else: else:
status_handler = None 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))) 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, installed, _ = context.handler.handle_simple(pacman.install_as_process(pkgpaths=to_install,
root_password=context.root_password, root_password=context.root_password,
@@ -1755,6 +1781,16 @@ class ArchManager(SoftwareManager):
self._update_progress(context, 95) self._update_progress(context, 95)
if installed: 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))) context.watcher.change_substatus(self.i18n['status.caching_data'].format(bold(context.name)))
if not context.maintainer: if not context.maintainer:
@@ -1887,16 +1923,16 @@ class ArchManager(SoftwareManager):
watcher.change_substatus(self.i18n['arch.makepkg.optimizing']) watcher.change_substatus(self.i18n['arch.makepkg.optimizing'])
ArchCompilationOptimizer(arch_config, self.i18n, self.context.logger).optimize() 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() self.aur_client.clean_caches()
if not self._check_action_allowed(pkg, watcher): 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 handler = ProcessHandler(watcher) if not context else context.handler
if self._is_database_locked(handler, root_password): if self._is_database_locked(handler, root_password):
return False return TransactionResult(success=False, installed=[], removed=[])
if context: if context:
install_context = 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(), install_context = TransactionContext.gen_context_from(pkg=pkg, handler=handler, arch_config=read_config(),
root_password=root_password) root_password=root_password)
install_context.skip_opt_deps = False 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) 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) data = json.loads(data)
pkg.fill_cached_data(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: def _install_from_repository(self, context: TransactionContext) -> bool:
try: try:

View File

@@ -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 = Thread(target=_fill_ignored, args=(ignored,), daemon=True)
thread_ignored.start() 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': {}} pkgs = {'signed': {}, 'not_signed': {}}
current_pkg = {} 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']: if pkgs['signed'] or pkgs['not_signed']:
thread_ignored.join() thread_ignored.join()

View File

@@ -7,7 +7,7 @@ from threading import Thread
from typing import List, Set, Type, Tuple from typing import List, Set, Type, Tuple
from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements, \ from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements, \
UpgradeRequirement UpgradeRequirement, TransactionResult
from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.disk import DiskCacheLoader
from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.handler import ProcessWatcher, TaskManager
from bauh.api.abstract.model import PackageHistory, PackageUpdate, SoftwarePackage, PackageSuggestion, \ 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) 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() config = read_config()
@@ -310,7 +310,7 @@ class FlatpakManager(SoftwareManager):
body=self.i18n['flatpak.install.bad_install_level.body'].format(field=bold('installation_level'), body=self.i18n['flatpak.install.bad_install_level.body'].format(field=bold('installation_level'),
file=bold(CONFIG_FILE)), file=bold(CONFIG_FILE)),
type_=MessageType.ERROR) type_=MessageType.ERROR)
return False return TransactionResult(success=False, installed=[], removed=[])
pkg.installation = install_level pkg.installation = install_level
else: else:
@@ -333,14 +333,14 @@ class FlatpakManager(SoftwareManager):
user_password, valid = watcher.request_root_password() user_password, valid = watcher.request_root_password()
if not valid: if not valid:
watcher.print('Operation aborted') watcher.print('Operation aborted')
return False return TransactionResult(success=False, installed=[], removed=[])
else: else:
if not handler.handle_simple(flatpak.set_default_remotes('system', user_password)): if not handler.handle_simple(flatpak.set_default_remotes('system', user_password)):
watcher.show_message(title=self.i18n['error'].capitalize(), watcher.show_message(title=self.i18n['error'].capitalize(),
body=self.i18n['flatpak.remotes.system_flathub.error'], body=self.i18n['flatpak.remotes.system_flathub.error'],
type_=MessageType.ERROR) type_=MessageType.ERROR)
watcher.print("Operation cancelled") 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')) 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: except:
traceback.print_exc() traceback.print_exc()
return res pkg.installed = res
return TransactionResult(success=res, installed=[pkg] if res else [], removed=[])
def is_enabled(self): def is_enabled(self):
return self.enabled return self.enabled

View File

@@ -4,7 +4,8 @@ from datetime import datetime
from threading import Thread from threading import Thread
from typing import List, Set, Type 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.disk import DiskCacheLoader
from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.handler import ProcessWatcher, TaskManager
from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion, \ from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion, \
@@ -174,7 +175,7 @@ class SnapManager(SoftwareManager):
def get_history(self, pkg: SnapApplication) -> PackageHistory: def get_history(self, pkg: SnapApplication) -> PackageHistory:
raise Exception("'get_history' is not supported by {}".format(pkg.__class__.__name__)) 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() info_path = self.get_info_path()
if not info_path: if not info_path:
@@ -204,14 +205,16 @@ 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)
return res pkg.installed = res
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))
else: else:
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)
return res pkg.installed = res
return TransactionResult(success=res, installed=[pkg] if res else [], removed=[])
def is_enabled(self) -> bool: def is_enabled(self) -> bool:
return self.enabled return self.enabled

View File

@@ -17,7 +17,7 @@ from colorama import Fore
from requests import exceptions, Response from requests import exceptions, Response
from bauh.api.abstract.context import ApplicationContext 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.disk import DiskCacheLoader
from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.handler import ProcessWatcher, TaskManager
from bauh.api.abstract.model import SoftwarePackage, CustomSoftwareAction, PackageSuggestion, PackageUpdate, \ from bauh.api.abstract.model import SoftwarePackage, CustomSoftwareAction, PackageSuggestion, PackageUpdate, \
@@ -617,13 +617,13 @@ class WebApplicationManager(SoftwareManager):
pkg.name)) pkg.name))
traceback.print_exc() 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) continue_install, install_options = self._ask_install_options(pkg, watcher)
if not continue_install: if not continue_install:
watcher.print("Installation aborted by the user") watcher.print("Installation aborted by the user")
return False return TransactionResult(success=False, installed=[], removed=[])
watcher.change_substatus(self.i18n['web.env.checking']) watcher.change_substatus(self.i18n['web.env.checking'])
handler = ProcessHandler(watcher) handler = ProcessHandler(watcher)
@@ -635,18 +635,18 @@ class WebApplicationManager(SoftwareManager):
watcher.show_message(title=self.i18n['error'].capitalize(), watcher.show_message(title=self.i18n['error'].capitalize(),
body=self.i18n['web.install.global_nativefier.unavailable'].format(n=bold('Nativefier'), app=bold(pkg.name)) + '.', body=self.i18n['web.install.global_nativefier.unavailable'].format(n=bold('Nativefier'), app=bold(pkg.name)) + '.',
type_=MessageType.ERROR) 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()) 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] 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): 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): 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) 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) 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)), msg = '{}.{}.'.format(self.i18n['wen.install.error'].format(bold(pkg.name)),
self.i18n['web.install.nativefier.error.unknown'].format(bold(self.i18n['details'].capitalize()))) 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) 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) inner_dir = os.listdir(app_dir)
@@ -707,7 +707,7 @@ class WebApplicationManager(SoftwareManager):
msg = '{}.{}.'.format(self.i18n['wen.install.error'].format(bold(pkg.name)), msg = '{}.{}.'.format(self.i18n['wen.install.error'].format(bold(pkg.name)),
self.i18n['web.install.nativefier.error.inner_dir'].format(bold(app_dir))) self.i18n['web.install.nativefier.error.inner_dir'].format(bold(app_dir)))
watcher.show_message(title=self.i18n['error'], body=msg, type_=MessageType.ERROR) 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: # bringing the inner app folder to the 'installed' folder level:
inner_dir = '{}/{}'.format(app_dir, inner_dir[0]) inner_dir = '{}/{}'.format(app_dir, inner_dir[0])
@@ -763,7 +763,8 @@ class WebApplicationManager(SoftwareManager):
if install_options: if install_options:
pkg.options_set = 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: def _gen_desktop_entry_content(self, pkg: WebApplication) -> str:
return """ return """

View File

@@ -7,7 +7,7 @@ from threading import Thread
from typing import List, Set, Type, Tuple, Dict from typing import List, Set, Type, Tuple, Dict
from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext, UpgradeRequirements, \ from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext, UpgradeRequirements, \
UpgradeRequirement UpgradeRequirement, TransactionResult
from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.disk import DiskCacheLoader
from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.handler import ProcessWatcher, TaskManager
from bauh.api.abstract.model import SoftwarePackage, PackageUpdate, PackageHistory, PackageSuggestion, \ from bauh.api.abstract.model import SoftwarePackage, PackageUpdate, PackageHistory, PackageSuggestion, \
@@ -291,18 +291,22 @@ class GenericSoftwareManager(SoftwareManager):
if man: if man:
return man.uninstall(app, root_password, handler) 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) man = self._get_manager_for(app)
if man: if man:
ti = time.time() ti = time.time()
disk_loader = self.disk_loader_factory.new()
disk_loader.start()
try: try:
self.logger.info('Installing {}'.format(app)) self.logger.info('Installing {}'.format(app))
return man.install(app, root_password, handler) return man.install(app, root_password, disk_loader, handler)
except: except:
traceback.print_exc() traceback.print_exc()
return False return TransactionResult(success=False, installed=[], removed=[])
finally: finally:
disk_loader.stop_working()
disk_loader.join()
tf = time.time() tf = time.time()
self.logger.info('Installation of {}'.format(app) + 'took {0:.2f} minutes'.format((tf - ti)/60)) self.logger.info('Installation of {}'.format(app) + 'took {0:.2f} minutes'.format((tf - ti)/60))

View File

@@ -631,17 +631,20 @@ class InstallPackage(AsyncAction):
def run(self): def run(self):
if self.pkg: if self.pkg:
success = False res = {'success': False, 'installed': None, 'removed': None, 'pkg': self.pkg}
if self.pkg.model.supports_backup(): if self.pkg.model.supports_backup():
if not self.request_backup(read_config(), 'install', self.i18n, self.root_pwd): 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 return
try: 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 self.pkg.model.installed = True
if self.pkg.model.supports_disk_cache(): 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, icon_bytes=icon_data.get('bytes') if icon_data else None,
only_icon=False) only_icon=False)
except (requests.exceptions.ConnectionError, NoInternetException): except (requests.exceptions.ConnectionError, NoInternetException):
success = False res['success'] = False
self.print(self.i18n['internet.required']) self.print(self.i18n['internet.required'])
finally: finally:
self.signal_finished.emit({'success': success, 'pkg': self.pkg}) self.signal_finished.emit(res)
self.pkg = None self.pkg = None

View File

@@ -1211,7 +1211,23 @@ class ManageWindow(QWidget):
if self._can_notify_user(): if self._can_notify_user():
util.notify_user(msg='{} ({}) {}'.format(res['pkg'].model.name, res['pkg'].model.get_type(), self.i18n['installed'])) 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_bt_installed.setVisible(False)
self.ref_checkbox_only_apps.setVisible(False) self.ref_checkbox_only_apps.setVisible(False)
self.update_custom_actions() 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)), body=self.i18n['action.{}.fail'.format(res['action'])].format(bold(res['pkg'].model.name)),
type_=MessageType.ERROR) type_=MessageType.ERROR)