mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-10 00:44:16 +02:00
[ui] fix: upgrading: only requesting the root password if required | [flatpak] fix -> crashing when trying to downgrade
This commit is contained in:
13
CHANGELOG.md
13
CHANGELOG.md
@@ -27,11 +27,20 @@ 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
|
||||
### Features
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user