[ui] fix: upgrading: only requesting the root password if required | [flatpak] fix -> crashing when trying to downgrade

This commit is contained in:
Vinicius Moreira
2020-12-10 19:15:34 -03:00
parent d435480165
commit bef465a1ea
19 changed files with 213 additions and 132 deletions

View File

@@ -27,11 +27,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixes ### Fixes
- AppImage - AppImage
- missing **Exec** parameters on generated desktop entries [#152](https://github.com/vinifmor/bauh/issues/152) - 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 - bauh release notification not working properly
- Web: - Web
- installation options window size - 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 ## [0.9.9] 2020-12-02
### Features ### Features

View File

@@ -2,6 +2,7 @@ import json
import os import os
import shutil import shutil
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from enum import Enum
from pathlib import Path from pathlib import Path
from typing import List, Set, Type, Tuple, Optional from typing import List, Set, Type, Tuple, Optional
@@ -53,7 +54,7 @@ class UpgradeRequirement:
class UpgradeRequirements: class UpgradeRequirements:
def __init__(self, to_install: Optional[List[UpgradeRequirement]], to_remove: Optional[List[UpgradeRequirement]], 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_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 :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) 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): class SoftwareManager(ABC):
""" """
@@ -95,7 +105,7 @@ class SoftwareManager(ABC):
self.context = context self.context = context
@abstractmethod @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 words: the words typed by the user
:param disk_loader: a running disk loader thread that loads package data from the disk asynchronously :param disk_loader: a running disk loader thread that loads package data from the disk asynchronously
@@ -157,7 +167,7 @@ class SoftwareManager(ABC):
pass pass
@abstractmethod @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 pkg:
:param root_password: the root user password (if required) :param root_password: the root user password (if required)
@@ -192,7 +202,7 @@ class SoftwareManager(ABC):
pass pass
@abstractmethod @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 pkg:
:param root_password: the root user password (if required) :param root_password: the root user password (if required)
@@ -264,9 +274,9 @@ class SoftwareManager(ABC):
f.write(icon_bytes) f.write(icon_bytes)
@abstractmethod @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 action:
:param pkg: :param pkg:
:return: :return:

View File

@@ -17,7 +17,7 @@ from pkg_resources import parse_version
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 TransactionResult, SoftwareAction
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, \
@@ -416,7 +416,8 @@ class AppImageManager(SoftwareManager):
app_tuple = cursor.fetchone() app_tuple = cursor.fetchone()
if not app_tuple: 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: finally:
self._close_connection(DB_APPS_PATH, connection) self._close_connection(DB_APPS_PATH, connection)
@@ -582,7 +583,7 @@ class AppImageManager(SoftwareManager):
def can_work(self) -> bool: def can_work(self) -> bool:
return self._is_sqlite3_available() and self.file_downloader.can_work() 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 return False
def prepare(self, task_manager: TaskManager, root_password: str, internet_available: bool): def prepare(self, task_manager: TaskManager, root_password: str, internet_available: bool):

View File

@@ -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.body={} has only one published version.
appimage.downgrade.impossible.title=Impossible to downgrade appimage.downgrade.impossible.title=Impossible to downgrade
appimage.downgrade.install_version=It was not possible to install the version {} ({}) 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.error.uninstall_current_version=It was not possible to uninstall the current version of {}
appimage.history.0_version=version appimage.history.0_version=version
appimage.history.1_published_at=date appimage.history.1_published_at=date

View File

@@ -16,7 +16,7 @@ from typing import List, Set, Type, Tuple, Dict, Iterable, Optional
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 TransactionResult, SoftwareAction
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, \
@@ -2476,8 +2476,8 @@ class ArchManager(SoftwareManager):
def cache_to_disk(self, pkg: ArchPackage, icon_bytes: bytes, only_icon: bool): def cache_to_disk(self, pkg: ArchPackage, icon_bytes: bytes, only_icon: bool):
pass pass
def requires_root(self, action: str, pkg: ArchPackage): def requires_root(self, action: SoftwareAction, pkg: ArchPackage) -> bool:
if action == 'prepare': if action == SoftwareAction.PREPARE:
arch_config = read_config() arch_config = read_config()
if arch_config['refresh_mirrors_startup'] and mirrors.should_sync(self.logger): 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 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): 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()) task_man.register_task('arch_aur_cats', self.i18n['task.download_categories'].format('Arch'), get_icon_path())

View File

@@ -8,7 +8,7 @@ from threading import Thread
from typing import List, Set, Type, Tuple, Optional from typing import List, Set, Type, Tuple, Optional
from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements, \ 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.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, \
@@ -112,7 +112,7 @@ class FlatpakManager(SoftwareManager):
def _add_updates(self, version: str, output: list): def _add_updates(self, version: str, output: list):
output.append(flatpak.list_updates_as_str(version)) 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() version = flatpak.get_version()
updates = [] updates = []
@@ -174,7 +174,7 @@ class FlatpakManager(SoftwareManager):
watcher.change_progress(10) watcher.change_progress(10)
watcher.change_substatus(self.i18n['flatpak.downgrade.commits']) 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: # downgrade is not possible if the app current commit in the first one:
if history.pkg_status_idx == len(history.history) - 1: if history.pkg_status_idx == len(history.history) - 1:
@@ -295,9 +295,9 @@ class FlatpakManager(SoftwareManager):
else: else:
return {} 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) 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 status_idx = 0
commit_found = False commit_found = False
@@ -444,8 +444,8 @@ class FlatpakManager(SoftwareManager):
def can_work(self) -> bool: def can_work(self) -> bool:
return flatpak.is_installed() return flatpak.is_installed()
def requires_root(self, action: str, pkg: FlatpakApplication): def requires_root(self, action: SoftwareAction, pkg: FlatpakApplication) -> bool:
return action == 'downgrade' and pkg.installation == 'system' return action == SoftwareAction.DOWNGRADE and pkg.installation == 'system'
def prepare(self, task_manager: TaskManager, root_password: str, internet_available: bool): def prepare(self, task_manager: TaskManager, root_password: str, internet_available: bool):
Thread(target=read_config, args=(True,), daemon=True).start() Thread(target=read_config, args=(True,), daemon=True).start()

View File

@@ -233,7 +233,7 @@ def downgrade(app_ref: str, commit: str, installation: str, root_password: str)
wrong_error_phrases={'Warning'}) 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: try:
p = SimpleProcess(['flatpak', 'remote-info', '--log', origin, app_ref, '--{}'.format(installation)]) p = SimpleProcess(['flatpak', 'remote-info', '--log', origin, app_ref, '--{}'.format(installation)])
success, output = handler.handle_simple(p) 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() 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)) log = run_cmd('{} remote-info --log {} {} --{}'.format('flatpak', origin, app_ref, installation))
if not log: 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() commit[attr] = data[1].strip()
if attr == 'commit': if attr == 'commit':
commit[attr] = commit[attr][0:8] commit[attr] = commit[attr] if full_str else commit[attr][0:8]
if attr == 'date': if attr == 'date':
commit[attr] = datetime.strptime(commit[attr], '%Y-%m-%d %H:%M:%S +0000') commit[attr] = datetime.strptime(commit[attr], '%Y-%m-%d %H:%M:%S +0000')

View File

@@ -5,7 +5,7 @@ from threading import Thread
from typing import List, Set, Type, Optional, Tuple from typing import List, Set, Type, Optional, Tuple
from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext, UpgradeRequirements, \ from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext, UpgradeRequirements, \
TransactionResult TransactionResult, SoftwareAction
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, \
@@ -255,8 +255,8 @@ class SnapManager(SoftwareManager):
def can_work(self) -> bool: def can_work(self) -> bool:
return snap.is_installed() return snap.is_installed()
def requires_root(self, action: str, pkg: SnapApplication): def requires_root(self, action: SoftwareAction, pkg: SnapApplication) -> bool:
return action not in ('search', 'prepare') return action not in (SoftwareAction.PREPARE, SoftwareAction.SEARCH)
def refresh(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> bool: 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] return ProcessHandler(watcher).handle_simple(snap.refresh_and_stream(pkg.name, root_password))[0]

View File

@@ -17,7 +17,8 @@ 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, TransactionResult from bauh.api.abstract.controller import SoftwareManager, SearchResult, UpgradeRequirements, TransactionResult, \
SoftwareAction
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, \
@@ -801,7 +802,7 @@ class WebApplicationManager(SoftwareManager):
return False return False
def requires_root(self, action: str, pkg: SoftwarePackage): def requires_root(self, action: SoftwareAction, pkg: SoftwarePackage) -> bool:
return False return False
def _update_env_settings(self, task_manager: TaskManager = None): def _update_env_settings(self, task_manager: TaskManager = None):

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, TransactionResult UpgradeRequirement, TransactionResult, SoftwareAction
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, \
@@ -151,14 +151,14 @@ class GenericSoftwareManager(SoftwareManager):
res.installed.extend(apps_found.installed) res.installed.extend(apps_found.installed)
res.new.extend(apps_found.new) 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() ti = time.time()
self._wait_to_be_ready() self._wait_to_be_ready()
res = SearchResult([], [], 0) res = SearchResult([], [], 0)
if self.context.is_internet_available(): if self.context.is_internet_available():
norm_word = word.strip().lower() norm_word = words.strip().lower()
url_words = RE_IS_URL.match(norm_word) url_words = RE_IS_URL.match(norm_word)
disk_loader = self.disk_loader_factory.new() disk_loader = self.disk_loader_factory.new()
@@ -385,7 +385,7 @@ class GenericSoftwareManager(SoftwareManager):
if man: if man:
return man.cache_to_disk(pkg, icon_bytes=icon_bytes, only_icon=only_icon) 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 app is None:
if self.managers: if self.managers:
for man in self.managers: for man in self.managers:

View File

@@ -10,7 +10,7 @@ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy, QTableWid
from bauh import __app_name__ from bauh import __app_name__
from bauh.api.abstract.context import ApplicationContext 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.api.abstract.handler import TaskManager
from bauh.view.qt.components import new_spacer, QCustomToolbar from bauh.view.qt.components import new_spacer, QCustomToolbar
from bauh.view.qt.qt_utils import centralize from bauh.view.qt.qt_utils import centralize
@@ -51,7 +51,7 @@ class Prepare(QThread, TaskManager):
def run(self): def run(self):
root_pwd = None root_pwd = None
if self.manager.requires_root('prepare', None): if self.manager.requires_root(SoftwareAction.PREPARE, None):
ok, root_pwd = self.ask_password() ok, root_pwd = self.ask_password()
if not ok: if not ok:

View File

@@ -14,7 +14,7 @@ from PyQt5.QtWidgets import QWidget
from bauh import LOGS_PATH from bauh import LOGS_PATH
from bauh.api.abstract.cache import MemoryCache 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.handler import ProcessWatcher
from bauh.api.abstract.model import PackageStatus, SoftwarePackage, CustomSoftwareAction from bauh.api.abstract.model import PackageStatus, SoftwarePackage, CustomSoftwareAction
from bauh.api.abstract.view import MessageType, MultipleSelectComponent, InputOption, TextComponent, \ 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.qt.view_model import PackageView, PackageViewStatus
from bauh.view.util.translation import I18n 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): class AsyncAction(QThread, ProcessWatcher):
@@ -48,7 +48,7 @@ class AsyncAction(QThread, ProcessWatcher):
super(AsyncAction, self).__init__() super(AsyncAction, self).__init__()
self.wait_confirmation = False self.wait_confirmation = False
self.confirmation_res = None self.confirmation_res = None
self.root_password = None self.root_password = root_password
self.stop = False self.stop = False
def request_confirmation(self, title: str, body: str, components: List[ViewComponent] = None, def request_confirmation(self, title: str, body: str, components: List[ViewComponent] = None,
@@ -123,64 +123,78 @@ class AsyncAction(QThread, ProcessWatcher):
return False return False
def _generate_backup(self, app_config: dict, i18n: I18n, root_password: str) -> bool: def _generate_backup(self, app_config: dict, i18n: I18n, root_password: Optional[str]) -> bool:
if timeshift.is_available():
if app_config['backup']['mode'] not in ('only_one', 'incremental'): if app_config['backup']['mode'] not in ('only_one', 'incremental'):
self.show_message(title=self.i18n['error'].capitalize(), self.show_message(title=self.i18n['error'].capitalize(),
body='{}: {}'.format(self.i18n['action.backup.invalid_mode'], bold(app_config['backup']['mode'])), body='{}: {}'.format(self.i18n['action.backup.invalid_mode'],bold(app_config['backup']['mode'])),
type_=MessageType.ERROR) type_=MessageType.ERROR)
return False self.change_substatus('')
if not user.is_root() and not root_password:
root_pwd, valid = self.request_root_password()
else:
root_pwd, valid = root_password, True
if not valid:
return False return False
handler = ProcessHandler(self) handler = ProcessHandler(self)
if app_config['backup']['mode'] == 'only_one': if app_config['backup']['mode'] == 'only_one':
self.change_substatus('[{}] {}'.format(i18n['core.config.tab.backup'].lower(), i18n['action.backup.substatus.delete'])) 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)) deleted, _ = handler.handle_simple(timeshift.delete_all_snapshots(root_password))
if not deleted and not self.request_confirmation(title=i18n['core.config.tab.backup'], 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']), body='{}. {}'.format(i18n['action.backup.error.delete'],
i18n['action.backup.error.proceed']),
confirmation_label=i18n['yes'].capitalize(), confirmation_label=i18n['yes'].capitalize(),
deny_label=i18n['no'].capitalize()): deny_label=i18n['no'].capitalize()):
self.change_substatus('')
return False return False
self.change_substatus('[{}] {}'.format(i18n['core.config.tab.backup'].lower(), i18n['action.backup.substatus.create'])) 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'])) 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'], if not created and not self.request_confirmation(title=i18n['core.config.tab.backup'],
body='{}. {}'.format(i18n['action.backup.error.create'], body='{}. {}'.format(i18n['action.backup.error.create'],
i18n['action.backup.error.proceed']), i18n['action.backup.error.proceed']),
confirmation_label=i18n['yes'].capitalize(), confirmation_label=i18n['yes'].capitalize(),
deny_label=i18n['no'].capitalize()): deny_label=i18n['no'].capitalize()):
self.change_substatus('')
return False return False
self.change_substatus('')
return True return True
def request_backup(self, app_config: dict, key: str, i18n: I18n, root_password: str = None) -> bool: def _check_backup_requirements(self, app_config: dict, pkg: Optional[PackageView], action_key: Optional[str]) -> bool:
if bool(app_config['backup']['enabled']) and timeshift.is_available(): if pkg and not pkg.model.supports_backup():
val = app_config['backup'][key] if key else None return False
if val is None: # ask mode if action_key and app_config['backup'][action_key] is False:
if self.request_confirmation(title=i18n['core.config.tab.backup'], 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'], body=i18n['action.backup.msg'],
confirmation_label=i18n['yes'].capitalize(), confirmation_label=i18n['yes'].capitalize(),
deny_label=i18n['no'].capitalize()): deny_label=i18n['no'].capitalize())
res = self._generate_backup(app_config, i18n, root_password) else:
self.change_substatus('') return backup
return res
elif val is True: # direct mode 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]]:
res = self._generate_backup(app_config, i18n, root_password) if not backup_only:
self.change_substatus('') if not self._check_backup_requirements(app_config=app_config, pkg=pkg, action_key=action_key):
return res 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): class UpgradeSelected(AsyncAction):
@@ -373,15 +387,29 @@ class UpgradeSelected(AsyncAction):
traceback.print_exc() traceback.print_exc()
def run(self): def run(self):
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() valid_password, root_password = self._request_password()
else:
valid_password, root_password = True, None
if not valid_password: if not valid_password:
self.notify_finished({'success': False, 'updated': 0, 'types': set(), 'id': None}) self.notify_finished({'success': False, 'updated': 0, 'types': set(), 'id': None})
self.pkgs = None self.pkgs = None
return 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: if len(to_update) > 1:
self.disable_progress_controll() self.disable_progress_controll()
else: else:
@@ -440,24 +468,35 @@ class UpgradeSelected(AsyncAction):
app_config = read_config() 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: if app_config['disk']['trim']['after_upgrade'] is not False:
should_trim = app_config['disk']['trim']['after_upgrade'] or self._ask_for_trim() should_trim = app_config['disk']['trim']['after_upgrade'] or self._ask_for_trim()
else: else:
should_trim = False should_trim = False
# backup process ( if enabled, supported and accepted ) if should_trim and not root_user and root_password is None: # trim requires root and the password might have not being asked yet
if bool(app_config['backup']['enabled']) and app_config['backup']['upgrade'] in (True, None) and timeshift.is_available(): valid_password, root_password = self._request_password()
any_requires_bkp = False
for dep in requirements.to_upgrade: if not valid_password:
if dep.pkg.supports_backup(): self.notify_finished({'success': False, 'updated': 0, 'types': set(), 'id': None})
any_requires_bkp = True self.pkgs = None
break return
if any_requires_bkp: # performing backup
if not self.request_backup(app_config, 'upgrade', self.i18n, root_password): if should_backup:
self.notify_finished({'success': success, 'updated': updated, 'types': updated_types, 'id': None}) 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 self.pkgs = None
return return
@@ -497,7 +536,8 @@ class RefreshApps(AsyncAction):
def run(self): def run(self):
try: 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() refreshed_types = set()
if res: if res:
@@ -528,9 +568,13 @@ class UninstallPackage(AsyncAction):
def run(self): def run(self):
if self.pkg: if self.pkg:
if self.pkg.model.supports_backup(): proceed, _ = self.request_backup(action_key='uninstall',
if not self.request_backup(read_config(), 'uninstall', self.i18n, self.root_pwd): pkg=self.pkg,
self.notify_finished(False) 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.pkg = None
self.root_pwd = None self.root_pwd = None
return return
@@ -567,8 +611,13 @@ class DowngradePackage(AsyncAction):
if self.pkg: if self.pkg:
success = False success = False
if self.pkg.model.supports_backup(): proceed, _ = self.request_backup(action_key='downgrade',
if not self.request_backup(read_config(), 'downgrade', self.i18n, self.root_pwd): 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.notify_finished({'app': self.pkg, 'success': success})
self.pkg = None self.pkg = None
self.root_pwd = None self.root_pwd = None
@@ -630,7 +679,7 @@ class SearchPackages(AsyncAction):
if self.word: if self.word:
try: 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.installed)
search_res['pkgs_found'].extend(res.new) search_res['pkgs_found'].extend(res.new)
except NoInternetException: except NoInternetException:
@@ -654,9 +703,16 @@ class InstallPackage(AsyncAction):
if self.pkg: if self.pkg:
res = {'success': False, 'installed': None, 'removed': None, 'pkg': self.pkg} res = {'success': False, 'installed': None, 'removed': None, 'pkg': self.pkg}
if self.pkg.model.supports_backup(): proceed, _ = self.request_backup(action_key='install',
if not self.request_backup(read_config(), 'install', self.i18n, self.root_pwd): pkg=self.pkg,
i18n=self.i18n,
root_password=self.root_pwd,
app_config=read_config())
if not proceed:
self.signal_finished.emit(res) self.signal_finished.emit(res)
self.pkg = None
self.root_pwd = None
return return
try: try:
@@ -834,7 +890,7 @@ class ListWarnings(QThread):
self.man = man self.man = man
def run(self): def run(self):
warnings = self.man.list_warnings() warnings = self.man.list_warnings(internet_available=True)
if warnings: if warnings:
self.signal_warnings.emit(warnings) self.signal_warnings.emit(warnings)
@@ -949,8 +1005,12 @@ class CustomAction(AsyncAction):
def run(self): def run(self):
if self.custom_action.backup: if self.custom_action.backup:
app_config = read_config() proceed, _ = self.request_backup(app_config=read_config(),
if not self.request_backup(app_config, None, self.i18n, self.root_pwd): 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.notify_finished({'success': False, 'pkg': self.pkg, 'action': self.custom_action})
self.pkg = None self.pkg = None
self.custom_action = None self.custom_action = None

View File

@@ -14,7 +14,7 @@ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QHeaderView, QToolB
from bauh import LOGS_PATH from bauh import LOGS_PATH
from bauh.api.abstract.cache import MemoryCache from bauh.api.abstract.cache import MemoryCache
from bauh.api.abstract.context import ApplicationContext 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.model import SoftwarePackage
from bauh.api.abstract.view import MessageType from bauh.api.abstract.view import MessageType
from bauh.api.http import HttpClient from bauh.api.http import HttpClient
@@ -712,7 +712,7 @@ class ManageWindow(QWidget):
self._finish_search(res) self._finish_search(res)
def begin_uninstall(self, pkg: PackageView): 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: if not proceed:
return return
@@ -1160,7 +1160,7 @@ class ManageWindow(QWidget):
self.progress_controll_enabled = True self.progress_controll_enabled = True
def begin_downgrade(self, pkg: PackageView): 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: if not proceed:
return return
@@ -1281,7 +1281,7 @@ class ManageWindow(QWidget):
self.comp_manager.restore_state(ACTION_SEARCH) self.comp_manager.restore_state(ACTION_SEARCH)
dialog.show_message(title=self.i18n['warning'].capitalize(), body=self.i18n[res['error']], type_=MessageType.WARNING) 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 pwd = None
requires_root = self.manager.requires_root(action, pkg.model) requires_root = self.manager.requires_root(action, pkg.model)
@@ -1293,7 +1293,7 @@ class ManageWindow(QWidget):
return pwd, True return pwd, True
def install(self, pkg: PackageView): 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: if not proceed:
return return

View File

@@ -58,7 +58,7 @@ action.cancelled=lusuari ha cancel·lat loperació
action.disk_trim=Optimizing disc (TRIM) action.disk_trim=Optimizing disc (TRIM)
action.disk_trim.error=There was a problem while optimizing the disk action.disk_trim.error=There was a problem while optimizing the disk
action.failed=L'acció ha fallat 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.history.no_history.title=No history
action.ignore_updates.fail=It was not possible to ignore updates from {} action.ignore_updates.fail=It was not possible to ignore updates from {}
action.ignore_updates.success=Updates from {} will be ignored from now on action.ignore_updates.success=Updates from {} will be ignored from now on

View File

@@ -58,7 +58,7 @@ action.cancelled=Aktion vom Nutzer abgebrochen
action.disk_trim=Optimizing disc (TRIM) action.disk_trim=Optimizing disc (TRIM)
action.disk_trim.error=There was a problem while optimizing the disk action.disk_trim.error=There was a problem while optimizing the disk
action.failed=Aktion fehlgeschlagen 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.history.no_history.title=No history
action.ignore_updates.fail=It was not possible to ignore updates from {} action.ignore_updates.fail=It was not possible to ignore updates from {}
action.ignore_updates.success=Updates from {} will be ignored from now on action.ignore_updates.success=Updates from {} will be ignored from now on

View File

@@ -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.error=There was a problem while optimizing the disk
action.disk_trim=Optimizing disc (TRIM) action.disk_trim=Optimizing disc (TRIM)
action.failed=Action failed 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.history.no_history.title=No history
action.ignore_updates.fail=It was not possible to ignore updates from {} action.ignore_updates.fail=It was not possible to ignore updates from {}
action.ignore_updates.success=Updates from {} will be ignored from now on action.ignore_updates.success=Updates from {} will be ignored from now on

View File

@@ -58,7 +58,7 @@ action.cancelled=operación cancelada por el usuario
action.disk_trim=Optimizando el disco (TRIM) action.disk_trim=Optimizando el disco (TRIM)
action.disk_trim.error=Hubo un problema al optimizar el disco action.disk_trim.error=Hubo un problema al optimizar el disco
action.failed=Acción fallida 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.history.no_history.title=No history
action.ignore_updates.fail=No fue posible ignorar las actualizaciones de {} 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 action.ignore_updates.success=Las actualizaciones de {} serán ignoradas de ahora en adelante

View File

@@ -58,7 +58,7 @@ action.cancelled=operazione annullata dall'utente
action.disk_trim=Optimizing disc (TRIM) action.disk_trim=Optimizing disc (TRIM)
action.disk_trim.error=There was a problem while optimizing the disk action.disk_trim.error=There was a problem while optimizing the disk
action.failed=Azione non riuscita 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.history.no_history.title=No history
action.ignore_updates.fail=It was not possible to ignore updates from {} action.ignore_updates.fail=It was not possible to ignore updates from {}
action.ignore_updates.success=Updates from {} will be ignored from now on action.ignore_updates.success=Updates from {} will be ignored from now on

View File

@@ -58,7 +58,7 @@ action.cancelled=Операция отменена пользователем
action.disk_trim=Optimizing disc (TRIM) action.disk_trim=Optimizing disc (TRIM)
action.disk_trim.error=There was a problem while optimizing the disk action.disk_trim.error=There was a problem while optimizing the disk
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.history.no_history.title=No history
action.ignore_updates.fail=It was not possible to ignore updates from {} action.ignore_updates.fail=It was not possible to ignore updates from {}
action.ignore_updates.success=Updates from {} will be ignored from now on action.ignore_updates.success=Updates from {} will be ignored from now on