diff --git a/CHANGELOG.md b/CHANGELOG.md index 38e1bb96..9f6aa5d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,10 +27,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixes - AppImage - missing **Exec** parameters on generated desktop entries [#152](https://github.com/vinifmor/bauh/issues/152) -- Core + - crashing when trying to retrieve an AppImage history not available on the database anymore (downgrade is affected as well) +- Flatpak + - crashing when trying to downgrade (regression introduced in **0.9.9**) +- UI + - upgrading: only requesting the root password if required [#151](https://github.com/vinifmor/bauh/issues/151) + - install/uninstall/downgrade + specific backup settings could lead to crashing - bauh release notification not working properly -- Web: +- Web - installation options window size + +### Changes +- UI + - "backup" dialog is displayed before the "trim" dialog during the upgrade process (if both are enabled) ## [0.9.9] 2020-12-02 diff --git a/bauh/api/abstract/controller.py b/bauh/api/abstract/controller.py index 097643d8..335814b9 100644 --- a/bauh/api/abstract/controller.py +++ b/bauh/api/abstract/controller.py @@ -2,6 +2,7 @@ import json import os import shutil from abc import ABC, abstractmethod +from enum import Enum from pathlib import Path from typing import List, Set, Type, Tuple, Optional @@ -53,7 +54,7 @@ class UpgradeRequirement: class UpgradeRequirements: def __init__(self, to_install: Optional[List[UpgradeRequirement]], to_remove: Optional[List[UpgradeRequirement]], - to_upgrade: List[UpgradeRequirement], cannot_upgrade: List[UpgradeRequirement]): + to_upgrade: List[UpgradeRequirement], cannot_upgrade: Optional[List[UpgradeRequirement]]): """ :param to_install: additional packages that must be installed with the upgrade :param to_remove: non upgrading packages that should be removed due to conflicts with upgrading packages @@ -82,6 +83,15 @@ class TransactionResult: return TransactionResult(success=False, installed=None, removed=None) +class SoftwareAction(Enum): + PREPARE = 0 + SEARCH = 1 + INSTALL = 2 + UNINSTALL = 3 + UPGRADE = 4 + DOWNGRADE = 5 + + class SoftwareManager(ABC): """ @@ -95,7 +105,7 @@ class SoftwareManager(ABC): self.context = context @abstractmethod - def search(self, words: str, disk_loader: DiskCacheLoader, limit: int, is_url: bool) -> SearchResult: + def search(self, words: str, disk_loader: Optional[DiskCacheLoader], limit: int, is_url: bool) -> SearchResult: """ :param words: the words typed by the user :param disk_loader: a running disk loader thread that loads package data from the disk asynchronously @@ -157,7 +167,7 @@ class SoftwareManager(ABC): pass @abstractmethod - def uninstall(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher, disk_loader: DiskCacheLoader) -> TransactionResult: + def uninstall(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher, disk_loader: Optional[DiskCacheLoader]) -> TransactionResult: """ :param pkg: :param root_password: the root user password (if required) @@ -192,7 +202,7 @@ class SoftwareManager(ABC): pass @abstractmethod - def install(self, pkg: SoftwarePackage, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult: + def install(self, pkg: SoftwarePackage, root_password: str, disk_loader: Optional[DiskCacheLoader], watcher: ProcessWatcher) -> TransactionResult: """ :param pkg: :param root_password: the root user password (if required) @@ -264,9 +274,9 @@ class SoftwareManager(ABC): f.write(icon_bytes) @abstractmethod - def requires_root(self, action: str, pkg: Optional[SoftwarePackage]): + def requires_root(self, action: SoftwareAction, pkg: Optional[SoftwarePackage]) -> bool: """ - if a given action requires root privileges to be executed. Current actions are: 'install', 'uninstall', 'downgrade', 'search', 'refresh', 'prepare' + if a given action requires root privileges to be executed. 'install', 'uninstall', 'downgrade', 'search', 'prepare' :param action: :param pkg: :return: diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py index 5aea10b7..d55d6571 100644 --- a/bauh/gems/appimage/controller.py +++ b/bauh/gems/appimage/controller.py @@ -17,7 +17,7 @@ from pkg_resources import parse_version from bauh.api.abstract.context import ApplicationContext from bauh.api.abstract.controller import SoftwareManager, SearchResult, UpgradeRequirements, UpgradeRequirement, \ - TransactionResult + TransactionResult, SoftwareAction 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, \ @@ -416,7 +416,8 @@ class AppImageManager(SoftwareManager): app_tuple = cursor.fetchone() if not app_tuple: - raise Exception("Could not retrieve {} from the database {}".format(pkg, DB_APPS_PATH)) + self.logger.warning("Could not retrieve {} from the database {}".format(pkg, DB_APPS_PATH)) + return res finally: self._close_connection(DB_APPS_PATH, connection) @@ -582,7 +583,7 @@ class AppImageManager(SoftwareManager): def can_work(self) -> bool: return self._is_sqlite3_available() and self.file_downloader.can_work() - def requires_root(self, action: str, pkg: AppImage): + def requires_root(self, action: SoftwareAction, pkg: AppImage) -> bool: return False def prepare(self, task_manager: TaskManager, root_password: str, internet_available: bool): diff --git a/bauh/gems/appimage/resources/locale/en b/bauh/gems/appimage/resources/locale/en index 5394cf31..d69efe5a 100644 --- a/bauh/gems/appimage/resources/locale/en +++ b/bauh/gems/appimage/resources/locale/en @@ -14,7 +14,7 @@ appimage.downgrade.first_version={} is in its first published version appimage.downgrade.impossible.body={} has only one published version. appimage.downgrade.impossible.title=Impossible to downgrade appimage.downgrade.install_version=It was not possible to install the version {} ({}) -appimage.downgrade.unknown_version.body=It was not possible to identify {} current version in its versions history +appimage.downgrade.unknown_version.body={} current version was not found in its release history appimage.error.uninstall_current_version=It was not possible to uninstall the current version of {} appimage.history.0_version=version appimage.history.1_published_at=date diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index 2a06cd94..e365bb34 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -16,7 +16,7 @@ from typing import List, Set, Type, Tuple, Dict, Iterable, Optional import requests from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements, \ - TransactionResult + TransactionResult, SoftwareAction 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, \ @@ -2476,8 +2476,8 @@ class ArchManager(SoftwareManager): def cache_to_disk(self, pkg: ArchPackage, icon_bytes: bytes, only_icon: bool): pass - def requires_root(self, action: str, pkg: ArchPackage): - if action == 'prepare': + def requires_root(self, action: SoftwareAction, pkg: ArchPackage) -> bool: + if action == SoftwareAction.PREPARE: arch_config = read_config() if arch_config['refresh_mirrors_startup'] and mirrors.should_sync(self.logger): @@ -2485,7 +2485,7 @@ class ArchManager(SoftwareManager): return arch_config['sync_databases_startup'] and database.should_sync(arch_config, None, self.logger) - return action != 'search' + return action != SoftwareAction.SEARCH def _start_category_task(self, task_man: TaskManager): task_man.register_task('arch_aur_cats', self.i18n['task.download_categories'].format('Arch'), get_icon_path()) diff --git a/bauh/gems/flatpak/controller.py b/bauh/gems/flatpak/controller.py index ff9ce5c4..9fb6ef47 100644 --- a/bauh/gems/flatpak/controller.py +++ b/bauh/gems/flatpak/controller.py @@ -8,7 +8,7 @@ from threading import Thread from typing import List, Set, Type, Tuple, Optional from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements, \ - UpgradeRequirement, TransactionResult + UpgradeRequirement, TransactionResult, SoftwareAction 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, \ @@ -112,7 +112,7 @@ class FlatpakManager(SoftwareManager): def _add_updates(self, version: str, output: list): output.append(flatpak.list_updates_as_str(version)) - def read_installed(self, disk_loader: DiskCacheLoader, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = None) -> SearchResult: + def read_installed(self, disk_loader: Optional[DiskCacheLoader], limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = None) -> SearchResult: version = flatpak.get_version() updates = [] @@ -174,7 +174,7 @@ class FlatpakManager(SoftwareManager): watcher.change_progress(10) watcher.change_substatus(self.i18n['flatpak.downgrade.commits']) - history = self.get_history(pkg) + history = self.get_history(pkg, full_commit_str=True) # downgrade is not possible if the app current commit in the first one: if history.pkg_status_idx == len(history.history) - 1: @@ -295,9 +295,9 @@ class FlatpakManager(SoftwareManager): else: return {} - def get_history(self, pkg: FlatpakApplication) -> PackageHistory: + def get_history(self, pkg: FlatpakApplication, full_commit_str: bool = False) -> PackageHistory: pkg.commit = flatpak.get_commit(pkg.id, pkg.branch, pkg.installation) - commits = flatpak.get_app_commits_data(pkg.ref, pkg.origin, pkg.installation) + commits = flatpak.get_app_commits_data(pkg.ref, pkg.origin, pkg.installation, full_str=full_commit_str) status_idx = 0 commit_found = False @@ -444,8 +444,8 @@ class FlatpakManager(SoftwareManager): def can_work(self) -> bool: return flatpak.is_installed() - def requires_root(self, action: str, pkg: FlatpakApplication): - return action == 'downgrade' and pkg.installation == 'system' + def requires_root(self, action: SoftwareAction, pkg: FlatpakApplication) -> bool: + return action == SoftwareAction.DOWNGRADE and pkg.installation == 'system' def prepare(self, task_manager: TaskManager, root_password: str, internet_available: bool): Thread(target=read_config, args=(True,), daemon=True).start() diff --git a/bauh/gems/flatpak/flatpak.py b/bauh/gems/flatpak/flatpak.py index a273b4c1..e39f048a 100755 --- a/bauh/gems/flatpak/flatpak.py +++ b/bauh/gems/flatpak/flatpak.py @@ -233,7 +233,7 @@ def downgrade(app_ref: str, commit: str, installation: str, root_password: str) wrong_error_phrases={'Warning'}) -def get_app_commits(app_ref: str, origin: str, installation: str, handler: ProcessHandler) -> List[str]: +def get_app_commits(app_ref: str, origin: str, installation: str, handler: ProcessHandler) -> Optional[List[str]]: try: p = SimpleProcess(['flatpak', 'remote-info', '--log', origin, app_ref, '--{}'.format(installation)]) success, output = handler.handle_simple(p) @@ -245,7 +245,7 @@ def get_app_commits(app_ref: str, origin: str, installation: str, handler: Proce raise NoInternetException() -def get_app_commits_data(app_ref: str, origin: str, installation: str) -> List[dict]: +def get_app_commits_data(app_ref: str, origin: str, installation: str, full_str: bool = True) -> List[dict]: log = run_cmd('{} remote-info --log {} {} --{}'.format('flatpak', origin, app_ref, installation)) if not log: @@ -262,7 +262,7 @@ def get_app_commits_data(app_ref: str, origin: str, installation: str) -> List[d commit[attr] = data[1].strip() if attr == 'commit': - commit[attr] = commit[attr][0:8] + commit[attr] = commit[attr] if full_str else commit[attr][0:8] if attr == 'date': commit[attr] = datetime.strptime(commit[attr], '%Y-%m-%d %H:%M:%S +0000') diff --git a/bauh/gems/snap/controller.py b/bauh/gems/snap/controller.py index b641d417..36579a54 100644 --- a/bauh/gems/snap/controller.py +++ b/bauh/gems/snap/controller.py @@ -5,7 +5,7 @@ from threading import Thread from typing import List, Set, Type, Optional, Tuple from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext, UpgradeRequirements, \ - TransactionResult + TransactionResult, SoftwareAction 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, \ @@ -255,8 +255,8 @@ class SnapManager(SoftwareManager): def can_work(self) -> bool: return snap.is_installed() - def requires_root(self, action: str, pkg: SnapApplication): - return action not in ('search', 'prepare') + def requires_root(self, action: SoftwareAction, pkg: SnapApplication) -> bool: + return action not in (SoftwareAction.PREPARE, SoftwareAction.SEARCH) def refresh(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> bool: return ProcessHandler(watcher).handle_simple(snap.refresh_and_stream(pkg.name, root_password))[0] diff --git a/bauh/gems/web/controller.py b/bauh/gems/web/controller.py index 7372d386..11672c55 100644 --- a/bauh/gems/web/controller.py +++ b/bauh/gems/web/controller.py @@ -17,7 +17,8 @@ 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, TransactionResult +from bauh.api.abstract.controller import SoftwareManager, SearchResult, UpgradeRequirements, TransactionResult, \ + SoftwareAction 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, \ @@ -801,7 +802,7 @@ class WebApplicationManager(SoftwareManager): return False - def requires_root(self, action: str, pkg: SoftwarePackage): + def requires_root(self, action: SoftwareAction, pkg: SoftwarePackage) -> bool: return False def _update_env_settings(self, task_manager: TaskManager = None): diff --git a/bauh/view/core/controller.py b/bauh/view/core/controller.py index 5bb0a70d..0fca3f37 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, TransactionResult + UpgradeRequirement, TransactionResult, SoftwareAction 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, \ @@ -151,14 +151,14 @@ class GenericSoftwareManager(SoftwareManager): res.installed.extend(apps_found.installed) res.new.extend(apps_found.new) - def search(self, word: str, disk_loader: DiskCacheLoader = None, limit: int = -1, is_url: bool = False) -> SearchResult: + def search(self, words: str, disk_loader: DiskCacheLoader = None, limit: int = -1, is_url: bool = False) -> SearchResult: ti = time.time() self._wait_to_be_ready() res = SearchResult([], [], 0) if self.context.is_internet_available(): - norm_word = word.strip().lower() + norm_word = words.strip().lower() url_words = RE_IS_URL.match(norm_word) disk_loader = self.disk_loader_factory.new() @@ -385,7 +385,7 @@ class GenericSoftwareManager(SoftwareManager): if man: return man.cache_to_disk(pkg, icon_bytes=icon_bytes, only_icon=only_icon) - def requires_root(self, action: str, app: SoftwarePackage) -> bool: + def requires_root(self, action: SoftwareAction, app: SoftwarePackage) -> bool: if app is None: if self.managers: for man in self.managers: diff --git a/bauh/view/qt/prepare.py b/bauh/view/qt/prepare.py index 04b1287b..752bcd46 100644 --- a/bauh/view/qt/prepare.py +++ b/bauh/view/qt/prepare.py @@ -10,7 +10,7 @@ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy, QTableWid from bauh import __app_name__ from bauh.api.abstract.context import ApplicationContext -from bauh.api.abstract.controller import SoftwareManager +from bauh.api.abstract.controller import SoftwareManager, SoftwareAction from bauh.api.abstract.handler import TaskManager from bauh.view.qt.components import new_spacer, QCustomToolbar from bauh.view.qt.qt_utils import centralize @@ -51,7 +51,7 @@ class Prepare(QThread, TaskManager): def run(self): root_pwd = None - if self.manager.requires_root('prepare', None): + if self.manager.requires_root(SoftwareAction.PREPARE, None): ok, root_pwd = self.ask_password() if not ok: diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py index 1537eebe..444f8106 100644 --- a/bauh/view/qt/thread.py +++ b/bauh/view/qt/thread.py @@ -14,7 +14,7 @@ from PyQt5.QtWidgets import QWidget from bauh import LOGS_PATH from bauh.api.abstract.cache import MemoryCache -from bauh.api.abstract.controller import SoftwareManager, UpgradeRequirement, UpgradeRequirements +from bauh.api.abstract.controller import SoftwareManager, UpgradeRequirement, UpgradeRequirements, SoftwareAction from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.model import PackageStatus, SoftwarePackage, CustomSoftwareAction from bauh.api.abstract.view import MessageType, MultipleSelectComponent, InputOption, TextComponent, \ @@ -29,7 +29,7 @@ from bauh.view.qt import commons from bauh.view.qt.view_model import PackageView, PackageViewStatus from bauh.view.util.translation import I18n -RE_VERSION_IN_NAME = re.compile(r'\s+version\s+[\w\.]+\s*$') +RE_VERSION_IN_NAME = re.compile(r'\s+version\s+[\w.]+\s*$') class AsyncAction(QThread, ProcessWatcher): @@ -48,7 +48,7 @@ class AsyncAction(QThread, ProcessWatcher): super(AsyncAction, self).__init__() self.wait_confirmation = False self.confirmation_res = None - self.root_password = None + self.root_password = root_password self.stop = False def request_confirmation(self, title: str, body: str, components: List[ViewComponent] = None, @@ -123,64 +123,78 @@ class AsyncAction(QThread, ProcessWatcher): return False - def _generate_backup(self, app_config: dict, i18n: I18n, root_password: str) -> bool: - if timeshift.is_available(): - if app_config['backup']['mode'] not in ('only_one', 'incremental'): - self.show_message(title=self.i18n['error'].capitalize(), - body='{}: {}'.format(self.i18n['action.backup.invalid_mode'], bold(app_config['backup']['mode'])), - type_=MessageType.ERROR) - return False + def _generate_backup(self, app_config: dict, i18n: I18n, root_password: Optional[str]) -> bool: + if app_config['backup']['mode'] not in ('only_one', 'incremental'): + self.show_message(title=self.i18n['error'].capitalize(), + body='{}: {}'.format(self.i18n['action.backup.invalid_mode'],bold(app_config['backup']['mode'])), + type_=MessageType.ERROR) + self.change_substatus('') + return False - if not user.is_root() and not root_password: - root_pwd, valid = self.request_root_password() - else: - root_pwd, valid = root_password, True + handler = ProcessHandler(self) + if app_config['backup']['mode'] == 'only_one': + self.change_substatus('[{}] {}'.format(i18n['core.config.tab.backup'].lower(), i18n['action.backup.substatus.delete'])) + deleted, _ = handler.handle_simple(timeshift.delete_all_snapshots(root_password)) - if not valid: - return False - - handler = ProcessHandler(self) - if app_config['backup']['mode'] == 'only_one': - self.change_substatus('[{}] {}'.format(i18n['core.config.tab.backup'].lower(), i18n['action.backup.substatus.delete'])) - deleted, _ = handler.handle_simple(timeshift.delete_all_snapshots(root_pwd)) - - if not deleted and not self.request_confirmation(title=i18n['core.config.tab.backup'], - body='{}. {}'.format(i18n['action.backup.error.delete'], i18n['action.backup.error.proceed']), - confirmation_label=i18n['yes'].capitalize(), - deny_label=i18n['no'].capitalize()): - return False - - self.change_substatus('[{}] {}'.format(i18n['core.config.tab.backup'].lower(), i18n['action.backup.substatus.create'])) - created, _ = handler.handle_simple(timeshift.create_snapshot(root_pwd, app_config['backup']['type'])) - - if not created and not self.request_confirmation(title=i18n['core.config.tab.backup'], - body='{}. {}'.format(i18n['action.backup.error.create'], + if not deleted and not self.request_confirmation(title=i18n['core.config.tab.backup'], + body='{}. {}'.format(i18n['action.backup.error.delete'], i18n['action.backup.error.proceed']), confirmation_label=i18n['yes'].capitalize(), deny_label=i18n['no'].capitalize()): + self.change_substatus('') return False + self.change_substatus('[{}] {}'.format(i18n['core.config.tab.backup'].lower(), i18n['action.backup.substatus.create'])) + created, _ = handler.handle_simple(timeshift.create_snapshot(root_password, app_config['backup']['type'])) + + if not created and not self.request_confirmation(title=i18n['core.config.tab.backup'], + body='{}. {}'.format(i18n['action.backup.error.create'], + i18n['action.backup.error.proceed']), + confirmation_label=i18n['yes'].capitalize(), + deny_label=i18n['no'].capitalize()): + self.change_substatus('') + return False + + self.change_substatus('') return True - def request_backup(self, app_config: dict, key: str, i18n: I18n, root_password: str = None) -> bool: - if bool(app_config['backup']['enabled']) and timeshift.is_available(): - val = app_config['backup'][key] if key else None + def _check_backup_requirements(self, app_config: dict, pkg: Optional[PackageView], action_key: Optional[str]) -> bool: + if pkg and not pkg.model.supports_backup(): + return False - if val is None: # ask mode - if self.request_confirmation(title=i18n['core.config.tab.backup'], + if action_key and app_config['backup'][action_key] is False: + return False + + return bool(app_config['backup']['enabled']) and timeshift.is_available() + + def _should_backup(self, action_key: str, app_config: dict, i18n: I18n) -> bool: + # backup -> true: do not ask, only execute | false: do not ask or execute | None: ask + backup = app_config['backup'][action_key] if action_key else None + + if backup is None: + return self.request_confirmation(title=i18n['core.config.tab.backup'], body=i18n['action.backup.msg'], confirmation_label=i18n['yes'].capitalize(), - deny_label=i18n['no'].capitalize()): - res = self._generate_backup(app_config, i18n, root_password) - self.change_substatus('') - return res + deny_label=i18n['no'].capitalize()) + else: + return backup - elif val is True: # direct mode - res = self._generate_backup(app_config, i18n, root_password) - self.change_substatus('') - return res + def request_backup(self, action_key: Optional[str], pkg: Optional[PackageView], i18n: I18n, app_config: dict, root_password: Optional[str], backup_only: bool = False) -> Tuple[bool, Optional[str]]: + if not backup_only: + if not self._check_backup_requirements(app_config=app_config, pkg=pkg, action_key=action_key): + return True, root_password - return True + if not self._should_backup(action_key=action_key, app_config=app_config, i18n=i18n): + return True, root_password + + pwd = root_password + if not user.is_root() and pwd is None: + valid_password, pwd = self.request_root_password() + + if not valid_password: + return False, None + + return self._generate_backup(app_config=app_config, i18n=i18n, root_password=pwd), pwd class UpgradeSelected(AsyncAction): @@ -373,15 +387,29 @@ class UpgradeSelected(AsyncAction): traceback.print_exc() def run(self): - valid_password, root_password = self._request_password() + root_user = user.is_root() + to_update, upgrade_requires_root, bkp_supported = [], False, False + + for pkg in self.pkgs: + if pkg.model.update and not pkg.model.is_update_ignored() and pkg.update_checked: + to_update.append(pkg) + + if not bkp_supported and pkg.model.supports_backup(): + bkp_supported = True + + if not root_user and not upgrade_requires_root and self.manager.requires_root(SoftwareAction.UPGRADE, pkg.model): + upgrade_requires_root = True + + if upgrade_requires_root: + valid_password, root_password = self._request_password() + else: + valid_password, root_password = True, None if not valid_password: self.notify_finished({'success': False, 'updated': 0, 'types': set(), 'id': None}) self.pkgs = None return - to_update = [pkg for pkg in self.pkgs if pkg.model.update and not pkg.model.is_update_ignored() and pkg.update_checked] - if len(to_update) > 1: self.disable_progress_controll() else: @@ -440,26 +468,37 @@ class UpgradeSelected(AsyncAction): app_config = read_config() - # trim dialog + # backup dialog ( if enabled, supported and accepted ) + should_backup = bkp_supported + should_backup = should_backup and self._check_backup_requirements(app_config=app_config, pkg=None, action_key='upgrade') + should_backup = should_backup and self._should_backup(action_key='upgrade', app_config=app_config, i18n=self.i18n) + + # trim dialog ( if enabled and accepted ) if app_config['disk']['trim']['after_upgrade'] is not False: should_trim = app_config['disk']['trim']['after_upgrade'] or self._ask_for_trim() else: should_trim = False - # backup process ( if enabled, supported and accepted ) - if bool(app_config['backup']['enabled']) and app_config['backup']['upgrade'] in (True, None) and timeshift.is_available(): - any_requires_bkp = False + if should_trim and not root_user and root_password is None: # trim requires root and the password might have not being asked yet + valid_password, root_password = self._request_password() - for dep in requirements.to_upgrade: - if dep.pkg.supports_backup(): - any_requires_bkp = True - break + if not valid_password: + self.notify_finished({'success': False, 'updated': 0, 'types': set(), 'id': None}) + self.pkgs = None + return - if any_requires_bkp: - if not self.request_backup(app_config, 'upgrade', self.i18n, root_password): - self.notify_finished({'success': success, 'updated': updated, 'types': updated_types, 'id': None}) - self.pkgs = None - return + # performing backup + if should_backup: + proceed, root_password = self.request_backup(action_key='upgrade', + app_config=app_config, + i18n=self.i18n, + root_password=root_password, + pkg=None, + backup_only=True) + if not proceed: + self.notify_finished({'success': False, 'updated': 0, 'types': set(), 'id': None}) + self.pkgs = None + return self.change_substatus('') @@ -497,7 +536,8 @@ class RefreshApps(AsyncAction): def run(self): try: - res = self.manager.read_installed(pkg_types=self.pkg_types) + res = self.manager.read_installed(pkg_types=self.pkg_types, disk_loader=None, only_apps=False, + internet_available=True, limit=-1) refreshed_types = set() if res: @@ -528,12 +568,16 @@ class UninstallPackage(AsyncAction): def run(self): if self.pkg: - if self.pkg.model.supports_backup(): - if not self.request_backup(read_config(), 'uninstall', self.i18n, self.root_pwd): - self.notify_finished(False) - self.pkg = None - self.root_pwd = None - return + proceed, _ = self.request_backup(action_key='uninstall', + pkg=self.pkg, + i18n=self.i18n, + root_password=self.root_pwd, + app_config=read_config()) + if not proceed: + self.notify_finished({'success': False, 'removed': None, 'pkg': self.pkg}) + self.pkg = None + self.root_pwd = None + return try: res = self.manager.uninstall(self.pkg.model, self.root_pwd, self, None) @@ -567,12 +611,17 @@ class DowngradePackage(AsyncAction): if self.pkg: success = False - if self.pkg.model.supports_backup(): - if not self.request_backup(read_config(), 'downgrade', self.i18n, self.root_pwd): - self.notify_finished({'app': self.pkg, 'success': success}) - self.pkg = None - self.root_pwd = None - return + proceed, _ = self.request_backup(action_key='downgrade', + pkg=self.pkg, + i18n=self.i18n, + root_password=self.root_pwd, + app_config=read_config()) + + if not proceed: + self.notify_finished({'app': self.pkg, 'success': success}) + self.pkg = None + self.root_pwd = None + return try: success = self.manager.downgrade(self.pkg.model, self.root_pwd, self) @@ -630,7 +679,7 @@ class SearchPackages(AsyncAction): if self.word: try: - res = self.manager.search(self.word) + res = self.manager.search(words=self.word, disk_loader=None, limit=-1, is_url=False) search_res['pkgs_found'].extend(res.installed) search_res['pkgs_found'].extend(res.new) except NoInternetException: @@ -654,10 +703,17 @@ class InstallPackage(AsyncAction): if self.pkg: 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(res) - return + proceed, _ = self.request_backup(action_key='install', + pkg=self.pkg, + i18n=self.i18n, + root_password=self.root_pwd, + app_config=read_config()) + + if not proceed: + self.signal_finished.emit(res) + self.pkg = None + self.root_pwd = None + return try: transaction_res = self.manager.install(self.pkg.model, self.root_pwd, None, self) @@ -834,7 +890,7 @@ class ListWarnings(QThread): self.man = man def run(self): - warnings = self.man.list_warnings() + warnings = self.man.list_warnings(internet_available=True) if warnings: self.signal_warnings.emit(warnings) @@ -949,8 +1005,12 @@ class CustomAction(AsyncAction): def run(self): if self.custom_action.backup: - app_config = read_config() - if not self.request_backup(app_config, None, self.i18n, self.root_pwd): + proceed, _ = self.request_backup(app_config=read_config(), + action_key=None, + i18n=self.i18n, + root_password=self.root_pwd, + pkg=self.pkg) + if not proceed: self.notify_finished({'success': False, 'pkg': self.pkg, 'action': self.custom_action}) self.pkg = None self.custom_action = None diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py index fb4d348b..dfda10ec 100755 --- a/bauh/view/qt/window.py +++ b/bauh/view/qt/window.py @@ -14,7 +14,7 @@ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QHeaderView, QToolB from bauh import LOGS_PATH from bauh.api.abstract.cache import MemoryCache from bauh.api.abstract.context import ApplicationContext -from bauh.api.abstract.controller import SoftwareManager +from bauh.api.abstract.controller import SoftwareManager, SoftwareAction from bauh.api.abstract.model import SoftwarePackage from bauh.api.abstract.view import MessageType from bauh.api.http import HttpClient @@ -712,7 +712,7 @@ class ManageWindow(QWidget): self._finish_search(res) def begin_uninstall(self, pkg: PackageView): - pwd, proceed = self._ask_root_password('uninstall', pkg) + pwd, proceed = self._ask_root_password(SoftwareAction.UNINSTALL, pkg) if not proceed: return @@ -1160,7 +1160,7 @@ class ManageWindow(QWidget): self.progress_controll_enabled = True def begin_downgrade(self, pkg: PackageView): - pwd, proceed = self._ask_root_password('downgrade', pkg) + pwd, proceed = self._ask_root_password(SoftwareAction.DOWNGRADE, pkg) if not proceed: return @@ -1281,7 +1281,7 @@ class ManageWindow(QWidget): self.comp_manager.restore_state(ACTION_SEARCH) dialog.show_message(title=self.i18n['warning'].capitalize(), body=self.i18n[res['error']], type_=MessageType.WARNING) - def _ask_root_password(self, action: str, pkg: PackageView) -> Tuple[Optional[str], bool]: + def _ask_root_password(self, action: SoftwareAction, pkg: PackageView) -> Tuple[Optional[str], bool]: pwd = None requires_root = self.manager.requires_root(action, pkg.model) @@ -1293,7 +1293,7 @@ class ManageWindow(QWidget): return pwd, True def install(self, pkg: PackageView): - pwd, proceed = self._ask_root_password('install', pkg) + pwd, proceed = self._ask_root_password(SoftwareAction.INSTALL, pkg) if not proceed: return diff --git a/bauh/view/resources/locale/ca b/bauh/view/resources/locale/ca index 033dd19b..74115bd9 100644 --- a/bauh/view/resources/locale/ca +++ b/bauh/view/resources/locale/ca @@ -58,7 +58,7 @@ action.cancelled=l’usuari ha cancel·lat l’operació action.disk_trim=Optimizing disc (TRIM) action.disk_trim.error=There was a problem while optimizing the disk action.failed=L'acció ha fallat -action.history.no_history.body=There is no available history for {} +action.history.no_history.body=There is no history available for {} action.history.no_history.title=No history action.ignore_updates.fail=It was not possible to ignore updates from {} action.ignore_updates.success=Updates from {} will be ignored from now on diff --git a/bauh/view/resources/locale/de b/bauh/view/resources/locale/de index acce8a67..86df0dfb 100644 --- a/bauh/view/resources/locale/de +++ b/bauh/view/resources/locale/de @@ -58,7 +58,7 @@ action.cancelled=Aktion vom Nutzer abgebrochen action.disk_trim=Optimizing disc (TRIM) action.disk_trim.error=There was a problem while optimizing the disk action.failed=Aktion fehlgeschlagen -action.history.no_history.body=There is no available history for {} +action.history.no_history.body=There is no history available for {} action.history.no_history.title=No history action.ignore_updates.fail=It was not possible to ignore updates from {} action.ignore_updates.success=Updates from {} will be ignored from now on diff --git a/bauh/view/resources/locale/en b/bauh/view/resources/locale/en index 6022d3f3..3d9e1bf5 100644 --- a/bauh/view/resources/locale/en +++ b/bauh/view/resources/locale/en @@ -58,7 +58,7 @@ action.cancelled=operation cancelled by the user action.disk_trim.error=There was a problem while optimizing the disk action.disk_trim=Optimizing disc (TRIM) action.failed=Action failed -action.history.no_history.body=There is no available history for {} +action.history.no_history.body=There is no history available for {} action.history.no_history.title=No history action.ignore_updates.fail=It was not possible to ignore updates from {} action.ignore_updates.success=Updates from {} will be ignored from now on diff --git a/bauh/view/resources/locale/es b/bauh/view/resources/locale/es index 559709a7..d51a49c1 100644 --- a/bauh/view/resources/locale/es +++ b/bauh/view/resources/locale/es @@ -58,7 +58,7 @@ action.cancelled=operación cancelada por el usuario action.disk_trim=Optimizando el disco (TRIM) action.disk_trim.error=Hubo un problema al optimizar el disco action.failed=Acción fallida -action.history.no_history.body=There is no available history for {} +action.history.no_history.body=No hay historial disponible para {} action.history.no_history.title=No history action.ignore_updates.fail=No fue posible ignorar las actualizaciones de {} action.ignore_updates.success=Las actualizaciones de {} serán ignoradas de ahora en adelante diff --git a/bauh/view/resources/locale/it b/bauh/view/resources/locale/it index d826458f..2df5a280 100644 --- a/bauh/view/resources/locale/it +++ b/bauh/view/resources/locale/it @@ -58,7 +58,7 @@ action.cancelled=operazione annullata dall'utente action.disk_trim=Optimizing disc (TRIM) action.disk_trim.error=There was a problem while optimizing the disk action.failed=Azione non riuscita -action.history.no_history.body=There is no available history for {} +action.history.no_history.body=There is no history available for {} action.history.no_history.title=No history action.ignore_updates.fail=It was not possible to ignore updates from {} action.ignore_updates.success=Updates from {} will be ignored from now on diff --git a/bauh/view/resources/locale/ru b/bauh/view/resources/locale/ru index fcdd0df4..7334d122 100644 --- a/bauh/view/resources/locale/ru +++ b/bauh/view/resources/locale/ru @@ -58,7 +58,7 @@ action.cancelled=Операция отменена пользователем action.disk_trim=Optimizing disc (TRIM) action.disk_trim.error=There was a problem while optimizing the disk action.failed=Действие не удалось -action.history.no_history.body=There is no available history for {} +action.history.no_history.body=There is no history available for {} action.history.no_history.title=No history action.ignore_updates.fail=It was not possible to ignore updates from {} action.ignore_updates.success=Updates from {} will be ignored from now on