From e626b8df7edad3977e280e753cc41551410dd2db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Moreira?= Date: Fri, 20 Dec 2019 12:28:05 -0300 Subject: [PATCH] [config] finished moving --- CHANGELOG.md | 61 +++++++++++++------------- README.md | 50 +++++++++++++++++----- bauh/app.py | 22 +++++----- bauh/view/core/config.py | 83 +++++++++++++++--------------------- bauh/view/core/controller.py | 33 +++++++------- bauh/view/core/gems.py | 9 ++-- bauh/view/qt/gem_selector.py | 10 ++--- bauh/view/qt/styles.py | 2 +- bauh/view/qt/systray.py | 10 ++--- bauh/view/qt/window.py | 18 +++++--- 10 files changed, 157 insertions(+), 141 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 997c9a2c..7b4da41f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog -All notable changes to this project will be documented in this file. +All notable changes to this project will be documented in this file.uh-staging The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -13,35 +13,36 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Improvements - configuration file **~/.config/bauh/config.json** renamed to **~/.config/bauh/config.yml** -- some parameters were moved to the configuration file ( **~/.config/bauh/config.yml** ): - - `--cache-exp` and `--icon-exp` became: - ```cache: - data_expiration: - icon_expiration: - ``` - - `--locale` - - `--check-interval` became: - ``` - updates: - check_interval: - ``` - - `--system-notifications` -> `system_notifications` - - `--disk-cache` - - `--download-icons` - - `--check-packaging-once` - - `--sugs` ( renamed as `suggestions` ) - - `--max-displayed` - - `--download-mthread` -- some settings from **~/.config/bauh/config.json** were renamed in **~/.config/bauh/config.yml**: - - `enabled_gems` -> `gems` -- some environment variables were moved as fields in **~/.config/bauh/config.yml**: - - `BAUH_TRAY_DEFAULT_ICON_PATH` and `BAUH_TRAY_UPDATES_ICON_PATH` became: - ``` - tray: - icon_default: null, # path to the icon representing the default state - icon_updates: null # path to the icon representing the "new updates" state - ``` - +- some parameters and environment variable were moved to the configuration file ( **~/.config/bauh/config.yml** ) +``` +disk_cache: # old '--disk_cache' + enabled: true +download: + icons: true # old '--download-icons' + multithreaded: true # old '--download-mthread' +gems: null +locale: null # old '--locale' +memory_cache: + data_expiration: 3600 # old '--cache-exp' + icon_expiration: 300 # old '--icon-exp' +suggestions: + by_type: 10 # new -> defines the max number of suggestions by package type + enabled: true # old '--sugs' +system: + notifications: true # old '--system-notifications' + single_dependency_checking: false # old '---check-packaging-once' +ui: + style: null + table: + max_displayed: 50 # old '--max-displayed' + tray: + default_icon: null # old environment variable 'BAUH_TRAY_DEFAULT_ICON_PATH' + updates_icon: null # old environment variable 'BAUH_TRAY_UPDATES_ICON_PATH' +updates: + check_interval: 30 # old '--check-interval' + +``` +- The default update checking interval is now 30 seconds - AppImage: - cleaning the downloaded database files when **--reset** is passed as parameter - environment variables **BAUH_APPIMAGE_DB_UPDATER** and **BAUH_APPIMAGE_DB_UPDATER_TIME** dropped in favor of the new configuration file located at **~/.config/bauh/appimage.yml** diff --git a/README.md b/README.md index e208a019..6e81ad14 100644 --- a/README.md +++ b/README.md @@ -182,21 +182,51 @@ environment: - Debian-based systems ( using pip ): **beautifulsoup4**, **lxml** ### General settings -You can change some application settings via environment variables or arguments (type ```bauh --help``` to get more information). -- **BAUH_TRAY**: If the tray icon and update-check daemon should be created. Use **0** (disable, default) or **1** (enable). -- **BAUH_LOGS**: enable **bauh** logs (for debugging purposes). Use: **0** (disable, default) or **1** (enable) -- **BAUH_TRAY_DEFAULT_ICON_PATH**: define a custom icon for the tray mode ( absolute path) -- **BAUH_TRAY_UPDATES_ICON_PATH** define a custom updates icon for the tray mode ( absolute path) -### How to improve **bauh** performance +#### Environment variables / parameters +You can change some application settings via environment variables or arguments (type ```bauh --help``` to get more information). +- `BAUH_TRAY (--tray )`: If the tray icon and update-check daemon should be created. Use `0` (disable, default) or `1` (enable). +- `BAUH_LOGS (--logs )`: enable **bauh** logs (for debugging purposes). Use: `0` (disable, default) or `1` (enable) +- `--reset`: cleans all configurations and cached data stored in HOME directory + +#### General configuration file ( **/.config/bauh/config.yml** ) +``` +disk_cache: + enabled: true # allows bauh to save applications icons and data to the disk to load them faster when needed +download: + icons: true # allows bauh to download the applications icons when they are not saved on the disk + multithreaded: true # allows bauh to use a multithreaded download client installed on the system to download applications source files faster ( current only **aria2** is supported ) +gems: null # defines the enabled applications types managed by bauh ( a null value means all available ) +locale: null # defines a different translation for bauh ( a null value will retrieve the system's default locale ) +memory_cache: + data_expiration: 3600 # the interval in SECONDS that data cached in memory will live + icon_expiration: 300 # the interval in SECONDS that icons cached in memory will live +suggestions: + by_type: 10 # the maximum number of application suggestions that must be retrieved per type + enabled: true # if suggestions must be displayed when no application is installed +system: + notifications: true # if system popup should be displayed for some events. e.g: when there are updates, bauh will display a system popup + single_dependency_checking: false # if bauh should check only once if for the available technologies on the system. +ui: + style: null # the bauh QT styles. a null value will map to 'Fusion' or 'Breeze' ( depending on what is installed ) + table: + max_displayed: 50 # defines the maximum number of displayed applications on the table. + tray: # system tray settings + default_icon: null # defines a path to a custom icon + updates_icon: null # defines a path to a custom icon indicating updates +updates: + check_interval: 30 # the updates checking interval in SECONDS + +``` + +### How to improve the performance - Disable package types that you do not want to deal with ( via GUI ) -- If you don't care about restarting the app every time a new supported packaging technology is installed, set "check-packaging-once=1" (**bauh --check-packaging-once=1**). This can reduce the application response time up in some scenarios, since it won't need to recheck if the packaging type is available for every action you request. -- If you don't mind to see the applications icons, you can set "download-icons=0" (**bauh --download-icons=0**). The application may have a slight response improvement, since it will reduce the parallelism within it. -- Let the disk cache always enabled so **bauh** does not need to dynamically retrieve some data every time you launch it. +- If you don't care about restarting the app every time a new supported packaging technology is installed, enable `single_dependency_checking`. This can reduce the application response time up in some scenarios, since it won't need to recheck if required technologies are available for every action you request. +- If you don't mind to see the applications icons, you can disable `download: icons: false`. The application may have a slight response improvement, since it will reduce the IO and parallelism within it. +- Let the `disk_cache` always enabled so **bauh** does not need to dynamically retrieve data every time you launch it. ### Files and Logs -- Some application settings are stored in **~/.config/bauh/config.json** - Installation logs are saved at **/tmp/bauh/logs/install** - Some data about your installed applications are stored in **~/.cache/bauh** to load them faster ( default behavior ). diff --git a/bauh/app.py b/bauh/app.py index bf576d0a..7c2085c2 100755 --- a/bauh/app.py +++ b/bauh/app.py @@ -34,28 +34,27 @@ def main(): app_args.validate(args, logger) local_config = config.read_config(update_file=True) - config.validate(local_config, logger) - i18n_key, current_i18n = translation.get_locale_keys(local_config.locale) + i18n_key, current_i18n = translation.get_locale_keys(local_config['locale']) default_i18n = translation.get_locale_keys(DEFAULT_I18N_KEY)[1] if i18n_key != DEFAULT_I18N_KEY else {} i18n = I18n(i18n_key, current_i18n, DEFAULT_I18N_KEY, default_i18n) cache_cleaner = CacheCleaner() - cache_factory = DefaultMemoryCacheFactory(expiration_time=local_config.cache_exp, cleaner=cache_cleaner) - icon_cache = cache_factory.new(local_config.icon_exp) + cache_factory = DefaultMemoryCacheFactory(expiration_time=int(local_config['memory_cache']['data_expiration']), cleaner=cache_cleaner) + icon_cache = cache_factory.new(int(local_config['memory_cache']['icon_expiration'])) http_client = HttpClient(logger) context = ApplicationContext(i18n=i18n, http_client=http_client, - disk_cache=local_config.disk_cache, - download_icons=local_config.download_icons, + disk_cache=bool(local_config['disk_cache']['enabled']), + download_icons=bool(local_config['download']['icons']), app_root_dir=ROOT_DIR, cache_factory=cache_factory, - disk_loader_factory=DefaultDiskCacheLoaderFactory(disk_cache_enabled=local_config.disk_cache, logger=logger), + disk_loader_factory=DefaultDiskCacheLoaderFactory(disk_cache_enabled=bool(local_config['disk_cache']['enabled']), logger=logger), logger=logger, distro=util.get_distro(), - file_downloader=AdaptableFileDownloader(logger, local_config.download_mthread, + file_downloader=AdaptableFileDownloader(logger, bool(local_config['download']['multithreaded']), i18n, http_client)) managers = gems.load_managers(context=context, locale=i18n_key, config=local_config, default_locale=DEFAULT_I18N_KEY) @@ -72,8 +71,8 @@ def main(): app.setApplicationVersion(__version__) app.setWindowIcon(QIcon(resource.get_path('img/logo.svg'))) - if local_config.style: - app.setStyle(local_config.style) + if local_config['ui']['style']: + app.setStyle(str(local_config['ui']['style'])) else: if app.style().objectName().lower() not in {'fusion', 'breeze'}: app.setStyle('Fusion') @@ -91,8 +90,7 @@ def main(): tray_icon = TrayIcon(i18n=i18n, manager=manager, manage_window=manage_window, - check_interval=local_config.update_check_interval, - update_notification=local_config.system_notifications) + config=local_config) manage_window.set_tray_icon(tray_icon) tray_icon.show() diff --git a/bauh/view/core/config.py b/bauh/view/core/config.py index 90998a99..df00a272 100644 --- a/bauh/view/core/config.py +++ b/bauh/view/core/config.py @@ -12,67 +12,52 @@ from bauh.commons.config import read_config as read CONFIG_PATH = '{}/.config/{}'.format(Path.home(), __app_name__) FILE_PATH = '{}/config.yml'.format(CONFIG_PATH) -# TODO stopped here -class UpdatesSettings: - def __init__(self, check_interval: int, **kwargs): - self.check_interval = check_interval - - -class CacheSettings: - - def __init__(self, data_expiration: int, icon_expiration: int, **kwargs): - self.data_expiration = data_expiration - self.icon_expiration = icon_expiration - - -class Configuration: - - def __init__(self, gems: List[str], style: str, cache: dict, locale: str, - updates: dict, system_notifications: bool, disk_cache: bool, download_icons: bool, - check_packaging_once: bool, suggestions: bool, max_displayed: int, download_mthread: bool, - **kwargs): - self.gems = gems - self.style = style - self.cache = CacheSettings(**cache) - self.locale = locale - self.updates = UpdatesSettings(**updates) - self.system_notifications = system_notifications - self.disk_cache = disk_cache - self.download_icons = download_icons - self.check_packaging_once = check_packaging_once - self.suggestions = suggestions - self.max_displayed = max_displayed - self.download_mthread = download_mthread - - -def read_config(update_file: bool = False) -> Configuration: +def read_config(update_file: bool = False) -> dict: default = { 'gems': None, - 'style': None, - 'cache_exp': 60 * 60, - 'icon_exp': 60 * 5, + 'memory_cache': { + 'data_expiration': 60 * 60, + 'icon_expiration': 60 * 5 + }, 'locale': None, 'updates': { - 'check_interval': 60 + 'check_interval': 30 }, - 'system_notifications': True, - 'disk_cache': True, - 'download_icons': True, - 'check_packaging_once': False, - 'suggestions': True, - 'max_displayed': 50, # table - 'download_mthread': True # downloads + 'system': { + 'notifications': True, + 'single_dependency_checking': False + }, + 'disk_cache': { + 'enabled': True + }, + 'suggestions': { + 'enabled': True, + 'by_type': 10 + }, + 'ui': { + 'table': { + 'max_displayed': 50 + }, + 'tray': { + 'default_icon': None, + 'updates_icon': None + }, + 'style': None + }, + 'download': { + 'multithreaded': True, + 'icons': True + } } - obj = read(FILE_PATH, default, update_file=update_file, update_async=True) - return Configuration(**obj) + return read(FILE_PATH, default, update_file=update_file, update_async=True) -def save(config: Configuration): +def save(config: dict): Path(CONFIG_PATH).mkdir(parents=True, exist_ok=True) with open(FILE_PATH, 'w+') as f: - f.write(yaml.safe_dump(config.__dict__)) + f.write(yaml.safe_dump(config)) def remove_old_config(logger: logging.Logger): diff --git a/bauh/view/core/controller.py b/bauh/view/core/controller.py index 5a9e5dbe..786edb6b 100755 --- a/bauh/view/core/controller.py +++ b/bauh/view/core/controller.py @@ -1,7 +1,6 @@ import re import time import traceback -from argparse import Namespace from threading import Thread from typing import List, Set, Type @@ -11,26 +10,24 @@ from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.model import SoftwarePackage, PackageUpdate, PackageHistory, PackageSuggestion, PackageAction from bauh.api.exception import NoInternetException from bauh.commons import internet -from bauh.view.core.config import Configuration RE_IS_URL = re.compile(r'^https?://.+') -SUGGESTIONS_LIMIT = 10 - class GenericSoftwareManager(SoftwareManager): - def __init__(self, managers: List[SoftwareManager], context: ApplicationContext, config: Configuration): + def __init__(self, managers: List[SoftwareManager], context: ApplicationContext, config: dict): super(GenericSoftwareManager, self).__init__(context=context) self.managers = managers self.map = {t: m for m in self.managers for t in m.get_managed_types()} - self._available_cache = {} if config.check_packaging_once else None + self._available_cache = {} if config['system']['single_dependency_checking'] else None self.thread_prepare = None self.i18n = context.i18n self.disk_loader_factory = context.disk_loader_factory self.logger = context.logger self._already_prepared = [] self.working_managers = [] + self.config = config def reset_cache(self): if self._available_cache is not None: @@ -345,20 +342,22 @@ class GenericSoftwareManager(SoftwareManager): suggestions.extend(man_sugs) def list_suggestions(self, limit: int) -> List[PackageSuggestion]: - if self.managers and internet.is_available(self.context.http_client, self.context.logger): - suggestions, threads = [], [] - for man in self.managers: - t = Thread(target=self._fill_suggestions, args=(suggestions, man, SUGGESTIONS_LIMIT)) - t.start() - threads.append(t) + if bool(self.config['suggestions']['enabled']): + if self.managers and internet.is_available(self.context.http_client, self.context.logger): + suggestions, threads = [], [] + for man in self.managers: + t = Thread(target=self._fill_suggestions, args=(suggestions, man, int(self.config['suggestions']['by_type']))) + t.start() + threads.append(t) - for t in threads: - t.join() + for t in threads: + t.join() - if suggestions: - suggestions.sort(key=lambda s: s.priority.value, reverse=True) + if suggestions: + suggestions.sort(key=lambda s: s.priority.value, reverse=True) - return suggestions + return suggestions + return [] def execute_custom_action(self, action: PackageAction, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher): man = self._get_manager_for(pkg) diff --git a/bauh/view/core/gems.py b/bauh/view/core/gems.py index 1c1e3216..f0dd57f8 100644 --- a/bauh/view/core/gems.py +++ b/bauh/view/core/gems.py @@ -5,8 +5,7 @@ from typing import List from bauh import ROOT_DIR from bauh.api.abstract.controller import SoftwareManager, ApplicationContext -from bauh.view.core.config import Configuration -from bauh.view.util import util, translation +from bauh.view.util import translation def find_manager(member): @@ -20,7 +19,7 @@ def find_manager(member): return manager_found -def load_managers(locale: str, context: ApplicationContext, config: Configuration, default_locale: str) -> List[SoftwareManager]: +def load_managers(locale: str, context: ApplicationContext, config: dict, default_locale: str) -> List[SoftwareManager]: managers = [] for f in os.scandir(ROOT_DIR + '/gems'): @@ -44,10 +43,10 @@ def load_managers(locale: str, context: ApplicationContext, config: Configuratio man = manager_class(context=context) - if config.gems is None: + if config['gems'] is None: man.set_enabled(man.is_default_enabled()) else: - man.set_enabled(f.name in config.gems) + man.set_enabled(f.name in config['gems']) managers.append(man) diff --git a/bauh/view/qt/gem_selector.py b/bauh/view/qt/gem_selector.py index e765b5ef..e8bf9dae 100644 --- a/bauh/view/qt/gem_selector.py +++ b/bauh/view/qt/gem_selector.py @@ -4,7 +4,7 @@ from PyQt5.QtWidgets import QWidget, QLabel, QGridLayout, QPushButton from bauh import ROOT_DIR from bauh.api.abstract.view import MultipleSelectComponent, InputOption -from bauh.view.core.config import Configuration, save +from bauh.view.core.config import save from bauh.view.core.controller import GenericSoftwareManager from bauh.view.util import resource from bauh.view.qt import qt_utils, css @@ -14,7 +14,7 @@ from bauh.view.util.translation import I18n class GemSelectorPanel(QWidget): - def __init__(self, window: QWidget, manager: GenericSoftwareManager, i18n: I18n, config: Configuration, show_panel_after_restart: bool = False): + def __init__(self, window: QWidget, manager: GenericSoftwareManager, i18n: I18n, config: dict, show_panel_after_restart: bool = False): super(GemSelectorPanel, self).__init__() self.window = window self.manager = manager @@ -57,8 +57,8 @@ class GemSelectorPanel(QWidget): if m.is_enabled() and m in manager.working_managers: default.add(op) - if self.config.enabled_gems: - default_ops = {o for o in gem_options if o.value in self.config.enabled_gems} + if self.config['gems']: + default_ops = {o for o in gem_options if o.value in self.config['gems']} else: default_ops = default @@ -89,7 +89,7 @@ class GemSelectorPanel(QWidget): enabled = module in enabled_gems man.set_enabled(enabled) - self.config.enabled_gems = enabled_gems + self.config['gems'] = enabled_gems save(self.config) self.manager.reset_cache() diff --git a/bauh/view/qt/styles.py b/bauh/view/qt/styles.py index d15af320..d092bd76 100644 --- a/bauh/view/qt/styles.py +++ b/bauh/view/qt/styles.py @@ -35,7 +35,7 @@ class StylesComboBox(QComboBox): style = self.styles[idx] user_config = config.read_config() - user_config.style = style + user_config['ui']['style'] = style config.save(user_config) util.restart_app(self.show_panel_after_restart) diff --git a/bauh/view/qt/systray.py b/bauh/view/qt/systray.py index 201936c8..a20fb61d 100755 --- a/bauh/view/qt/systray.py +++ b/bauh/view/qt/systray.py @@ -36,13 +36,13 @@ class UpdateCheck(QThread): class TrayIcon(QSystemTrayIcon): - def __init__(self, i18n: I18n, manager: SoftwareManager, manage_window: ManageWindow, check_interval: int = 60, update_notification: bool = True): + def __init__(self, i18n: I18n, manager: SoftwareManager, manage_window: ManageWindow, config: dict): super(TrayIcon, self).__init__() self.i18n = i18n self.manager = manager - self.icon_default = QIcon(os.getenv('BAUH_TRAY_DEFAULT_ICON_PATH', resource.get_path('img/logo.png'))) - self.icon_update = QIcon(os.getenv('BAUH_TRAY_UPDATES_ICON_PATH', resource.get_path('img/logo_update.png'))) + self.icon_default = QIcon(config['ui']['tray']['default_icon'] or resource.get_path('img/logo.png')) + self.icon_update = QIcon(config['ui']['tray']['updates_icon'] or resource.get_path('img/logo_update.png')) self.setIcon(self.icon_default) self.menu = QMenu() @@ -60,12 +60,12 @@ class TrayIcon(QSystemTrayIcon): self.manage_window = None self.dialog_about = None - self.check_thread = UpdateCheck(check_interval=check_interval, manager=self.manager) + self.check_thread = UpdateCheck(check_interval=int(config['updates']['check_interval']), manager=self.manager) self.check_thread.signal.connect(self.notify_updates) self.check_thread.start() self.last_updates = set() - self.update_notification = update_notification + self.update_notification = bool(config['system']['notifications']) self.lock_notify = Lock() self.activated.connect(self.handle_click) diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py index 729292bd..0009ff73 100755 --- a/bauh/view/qt/window.py +++ b/bauh/view/qt/window.py @@ -17,7 +17,6 @@ from bauh.api.abstract.model import SoftwarePackage, PackageAction from bauh.api.abstract.view import MessageType from bauh.api.http import HttpClient from bauh.commons.html import bold -from bauh.view.core.config import Configuration from bauh.view.core.controller import GenericSoftwareManager from bauh.view.qt import dialog, commons, qt_utils from bauh.view.qt.about import AboutDialog @@ -51,7 +50,7 @@ class ManageWindow(QWidget): signal_user_res = pyqtSignal(bool) signal_table_update = pyqtSignal() - def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager, screen_size, config: Configuration, + def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager, screen_size, config: dict, context: ApplicationContext, http_client: HttpClient, logger: logging.Logger, tray_icon=None): super(ManageWindow, self).__init__() self.i18n = i18n @@ -62,7 +61,7 @@ class ManageWindow(QWidget): self.pkgs = [] # packages current loaded in the table self.pkgs_available = [] # all packages loaded in memory self.pkgs_installed = [] # cached installed packages - self.display_limit = config.max_displayed + self.display_limit = config['ui']['table']['max_displayed'] self.icon_cache = icon_cache self.screen_size = screen_size self.config = config @@ -208,7 +207,9 @@ class ManageWindow(QWidget): self.layout.addWidget(self.toolbar) - self.table_apps = AppsTable(self, self.icon_cache, disk_cache=self.config.disk_cache, download_icons=self.config.download_icons) + self.table_apps = AppsTable(self, self.icon_cache, + disk_cache=bool(self.config['disk_cache']['enabled']), + download_icons=bool(self.config['download']['icons'])) self.table_apps.change_headers_policy() self.layout.addWidget(self.table_apps) @@ -260,7 +261,7 @@ class ManageWindow(QWidget): self.thread_apply_filters.signal_table.connect(self._update_table_and_upgrades) self.signal_table_update.connect(self.thread_apply_filters.stop_waiting) - self.thread_install = InstallPackage(manager=self.manager, disk_cache=self.config.disk_cache, icon_cache=self.icon_cache, i18n=self.i18n) + self.thread_install = InstallPackage(manager=self.manager, disk_cache=bool(self.config['disk_cache']['enabled']), icon_cache=self.icon_cache, i18n=self.i18n) self._bind_async_action(self.thread_install, finished_call=self._finish_install) self.thread_animate_progress = AnimateProgress() @@ -308,7 +309,7 @@ class ManageWindow(QWidget): self.types_changed = False self.dialog_about = None - self.first_refresh = config.suggestions + self.first_refresh = bool(config['suggestions']['enabled']) self.thread_warnings = ListWarnings(man=manager, i18n=i18n) self.thread_warnings.signal_warnings.connect(self._show_warnings) @@ -533,6 +534,9 @@ class ManageWindow(QWidget): only_pkg_type = len([p for p in self.pkgs if p.model.get_type() == pkgv.model.get_type()]) >= 2 self.recent_uninstall = True self.refresh_apps(pkg_types={pkgv.model.__class__} if only_pkg_type else None) + + if self.tray_icon: + self.tray_icon.verify_updates() else: if self._can_notify_user(): util.notify_user('{}: {}'.format(pkgv.model.name, self.i18n['notification.uninstall.failed'])) @@ -540,7 +544,7 @@ class ManageWindow(QWidget): self.checkbox_console.setChecked(True) def _can_notify_user(self): - return self.config.system_notifications and (self.isHidden() or self.isMinimized()) + return bool(self.config['system']['notifications']) and (self.isHidden() or self.isMinimized()) def _finish_downgrade(self, res: dict): self.finish_action()