diff --git a/CHANGELOG.md b/CHANGELOG.md index 02d4a88f..112b2f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,70 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [0.9.12] 2021-01-19 +### Features +- Arch + - AUR + - [rebuild-detector](https://github.com/maximbaz/rebuild-detector) integration [#139](https://github.com/vinifmor/bauh/issues/139) + - if a package needs to be rebuilt, it will be marked for update (rebuild-detector must be installed on your system, but it is not a hard requirement). + - if you hold the mouse over the package 'version' (on the table), a tip will be displayed with the message "It needs to be reinstalled". + - this integration can be controlled though the new settings property **aur_rebuild_detector** (default: true). +
+ +
+ + - new package actions to Allow/Ignore rebuild check for a specific package ++ +
+ ++ +
+ + - new settings property **aur_rebuild_detector_no_bin** to ignore binary packages when checking with rebuild-detector (e.g: package-bin ). Default: true ++ +
+ + - new custom action to quickly reinstall a package ++ +
+ +### Improvements +- Arch + - repositories/AUR search time (=~ -70%) + - new category to filter packages removed from AUR (only available for packages installed from AUR through bauh) ++ +
+ +- Core + - saving settings time (=~ -11%) + - internet checking time (=~ -58%) +- UI + - not displaying the number of packages when none is displayed / available + - minor improvements + +### Fixes +- Arch + - crashing when information of a given package is not available + - displaying "provided" repository packages on the search results (e.g: **nvidia** would appear as a package) + - calling pacman to read installed packages when "Repositories" and "AUR" properties are set to "false" (it would not display the packages, but the call was unnecessary being done) + - not displaying installed AUR packages when AUR support is disabled + - displaying packages removed from AUR as AUR packages + - downloading AUR index during the initialization process when AUR support is disabled +- Flatpak + - crashing for version 1.10 [#167](https://github.com/vinifmor/bauh/issues/167) + - crashing when trying to retrieve size of runtimes subcomponents [#164](https://github.com/vinifmor/bauh/issues/164) +- UI + - initialization dialog hanging sometimes (due to thread locking) + - settings dialog hangs sometimes when the button "Change" is clicked + - displaying a popup when information of a given package is not available + - wrong package type icon size depending on resolution + + ## [0.9.11] 2020-12-30 ### New system requirements - **python-dateutil**: better Python library for date handling (install the equivalent package for your Linux distribution before upgrading bauh) diff --git a/README.md b/README.md index 93108283..77a37e08 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ database: - Repository packages supported actions: search, install, uninstall, launch and ignore updates - AUR packages supported actions: search, install, uninstall, downgrade, launch, history and ignore updates - It handles conflicts, missing / optional packages installations, and several providers scenarios +- [rebuild-detector](https://github.com/maximbaz/rebuild-detector) integration (AUR only) - Automatically makes simple package compilation improvements: a) if **MAKEFLAGS** is not set in **/etc/makepkg.conf**, @@ -187,6 +188,8 @@ database: - **clean cache**: it cleans the pacman cache directory (default: `/var/cache/pacman/pkg`) - **mark PKGBUILD as editable**: it marks a given PKGBUILD of a package as editable (a popup with the PKGBUILD will be displayed before upgrading/downgrading this package). Action only available when the configuration property **edit_aur_pkgbuild** is not **false**. - **unmark PKGBUILD as editable**: reverts the action described above. Action only available when the configuration property **edit_aur_pkgbuild** is not **false**. + - **allow reinstallation check**: it allows to check if a given AUR packages requires to be rebuilt + - **ignore reinstallation check**: it does not to check if a given AUR packages requires to be rebuilt - **check Snaps support**: checks if the Snapd services are properly enabled. - Installed AUR packages have their **PKGBUILD** files cached at **~/.cache/bauh/arch/installed/$pkgname** - Packages with ignored updates are defined at **~/.config/bauh/arch/updates_ignored.txt** @@ -211,6 +214,7 @@ check_dependency_breakage: true # if, during the verification of the update requ suggest_unneeded_uninstall: false # if the dependencies apparently no longer necessary associated with the uninstalled packages should be suggested for uninstallation. When this property is enabled it automatically disables the property 'suggest_optdep_uninstall'. Default: false (to prevent new users from making mistakes) suggest_optdep_uninstall: false # if the optional dependencies associated with uninstalled packages should be suggested for uninstallation. Only the optional dependencies that are not dependencies of other packages will be suggested. Default: false (to prevent new users from making mistakes) categories_exp: 24 # It defines the expiration time (in HOURS) of the packages categories mapping file stored in disc. Use 0 so that it is always updated during initialization. +aur_rebuild_detector: true # it checks if packages built with old library versions require to be rebuilt. If a package needs to be rebuilt, it will be marked for update ('rebuild-detector' must be installed). Default: true. ``` - Required dependencies: - **pacman** diff --git a/bauh/__init__.py b/bauh/__init__.py index 99b39f79..4cd72d50 100644 --- a/bauh/__init__.py +++ b/bauh/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.9.11' +__version__ = '0.9.12' __app_name__ = 'bauh' import os diff --git a/bauh/api/abstract/controller.py b/bauh/api/abstract/controller.py index aaa18d53..4fbe1635 100644 --- a/bauh/api/abstract/controller.py +++ b/bauh/api/abstract/controller.py @@ -28,6 +28,17 @@ def __init__(self, installed: Optional[List[SoftwarePackage]], new: Optional[Lis self.new = new self.total = total + def update_total(self): + total = 0 + + if self.installed: + total += len(self.installed) + + if self.new: + total += len(self.new) + + self.total = total + @classmethod def empty(cls): return cls(installed=[], new=[], total=0) diff --git a/bauh/api/abstract/disk.py b/bauh/api/abstract/disk.py index fa2e8413..82b785a9 100644 --- a/bauh/api/abstract/disk.py +++ b/bauh/api/abstract/disk.py @@ -41,3 +41,7 @@ def map(self, pkg_type: Type[SoftwarePackage], cache: MemoryCache): :return: """ pass + + @abstractmethod + def new(self) -> DiskCacheLoader: + pass diff --git a/bauh/api/abstract/model.py b/bauh/api/abstract/model.py index 8c6dad1a..e569ee6e 100644 --- a/bauh/api/abstract/model.py +++ b/bauh/api/abstract/model.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from enum import Enum -from typing import List +from typing import List, Optional from bauh.api.constants import CACHE_PATH @@ -223,6 +223,12 @@ def get_display_name(self) -> str: """ return self.name + def get_update_tip(self) -> Optional[str]: + """ + custom 'version' update tooltip + """ + return + @abstractmethod def supports_backup(self) -> bool: pass diff --git a/bauh/api/abstract/view.py b/bauh/api/abstract/view.py index 30743b38..d9155c6f 100644 --- a/bauh/api/abstract/view.py +++ b/bauh/api/abstract/view.py @@ -33,18 +33,6 @@ def __init__(self): super(SpacerComponent, self).__init__(id_=None) -class PanelComponent(ViewComponent): - - def __init__(self, components: List[ViewComponent], id_: str = None): - super(PanelComponent, self).__init__(id_=id_) - self.components = components - self.component_map = {c.id: c for c in components if c.id is not None} if components else None - - def get_component(self, id_: str) -> ViewComponent: - if self.component_map: - return self.component_map.get(id_) - - class InputViewComponent(ViewComponent): """ Represents an component which needs a user interaction to provide its value @@ -96,6 +84,7 @@ def __init__(self, type_: SelectViewType, label: str, options: List[InputOption] self.label = label self.options = options self.value = default_option + self.init_value = default_option self.max_per_line = max_per_line self.tooltip = tooltip self.max_width = max_width @@ -105,6 +94,9 @@ def get_selected(self): if self.value: return self.value.value + def changed(self) -> bool: + return self.init_value != self.value + class MultipleSelectComponent(InputViewComponent): @@ -214,10 +206,26 @@ def __init__(self, components: List[ViewComponent], label: str = None, spaces: b self.components = components self.component_map = {c.id: c for c in components if c.id} if components else None - def get_component(self, id_: str) -> ViewComponent: + def get_component(self, id_: str) -> Optional[ViewComponent]: if self.component_map: return self.component_map.get(id_) + def get_single_select_component(self, id_: str) -> Optional[SingleSelectComponent]: + comp = self.get_component(id_) + + if comp: + if not isinstance(comp, SingleSelectComponent): + raise Exception("'{}' is not a {}".format(id_, SingleSelectComponent.__class__.__name__)) + return comp + + def get_form_component(self, id_: str) -> Optional["FormComponent"]: + comp = self.get_component(id_) + + if comp: + if not isinstance(comp, FormComponent): + raise Exception("'{}' is not a {}".format(id_, FormComponent.__class__.__name__)) + return comp + class FileChooserComponent(ViewComponent): @@ -281,3 +289,23 @@ def __init__(self, id_: str, label: str, tooltip: str, min_value: float, max_val self.step = step_value self.value = value self.max_width = max_width + + +class PanelComponent(ViewComponent): + + def __init__(self, components: List[ViewComponent], id_: str = None): + super(PanelComponent, self).__init__(id_=id_) + self.components = components + self.component_map = {c.id: c for c in components if c.id is not None} if components else None + + def get_component(self, id_: str) -> Optional[ViewComponent]: + if self.component_map: + return self.component_map.get(id_) + + def get_form_component(self, id_: str) -> Optional[FormComponent]: + comp = self.get_component(id_) + + if comp: + if not isinstance(comp, FormComponent): + raise Exception("'{}' is not a {}".format(id_, FormComponent.__class__.__name__)) + return comp diff --git a/bauh/commons/internet.py b/bauh/commons/internet.py index 1587c90e..de5bc61a 100644 --- a/bauh/commons/internet.py +++ b/bauh/commons/internet.py @@ -1,4 +1,4 @@ -import http.client as http_client +import socket class InternetChecker: @@ -10,11 +10,9 @@ def is_available(self) -> bool: if self.offline: return False - conn = http_client.HTTPConnection("www.google.com", timeout=5) try: - conn.request("HEAD", "/") - conn.close() + socket.gethostbyname('google.com') return True except: - conn.close() return False + diff --git a/bauh/commons/system.py b/bauh/commons/system.py index f96342c1..77735ed0 100644 --- a/bauh/commons/system.py +++ b/bauh/commons/system.py @@ -25,7 +25,7 @@ SIZE_MULTIPLIERS = ((0.001, 'Kb'), (0.000001, 'Mb'), (0.000000001, 'Gb'), (0.000000000001, 'Tb')) -def gen_env(global_interpreter: bool, lang: str = DEFAULT_LANG, extra_paths: Set[str] = None) -> dict: +def gen_env(global_interpreter: bool, lang: str = DEFAULT_LANG, extra_paths: Optional[Set[str]] = None) -> dict: custom_env = dict(os.environ) if lang: @@ -33,6 +33,7 @@ def gen_env(global_interpreter: bool, lang: str = DEFAULT_LANG, extra_paths: Set if global_interpreter: # to avoid subprocess calls to the virtualenv python interpreter instead of the global one. custom_env['PATH'] = GLOBAL_INTERPRETER_PATH + else: custom_env['PATH'] = PATH @@ -322,11 +323,22 @@ def check_enabled_services(*names: str) -> Dict[str, bool]: return {s: status[i].strip().lower() == 'enabled' for i, s in enumerate(names) if s} -def execute(cmd: str, shell: bool = False, cwd: Optional[str] = None, output: bool = True) -> Tuple[int, Optional[str]]: - p = subprocess.run(args=cmd.split(' ') if not shell else [cmd], - stdout=subprocess.PIPE if output else subprocess.DEVNULL, - stderr=subprocess.STDOUT if output else subprocess.DEVNULL, - shell=shell, - cwd=cwd) +def execute(cmd: str, shell: bool = False, cwd: Optional[str] = None, output: bool = True, custom_env: Optional[dict] = None, stdin: bool = True) -> Tuple[int, Optional[str]]: + params = { + 'args': cmd.split(' ') if not shell else [cmd], + 'stdout': subprocess.PIPE if output else subprocess.DEVNULL, + 'stderr': subprocess.STDOUT if output else subprocess.DEVNULL, + 'shell': shell + } + + if not stdin: + params['stdin'] = subprocess.DEVNULL + + if cwd is not None: + params['cwd'] = cwd + + if custom_env is not None: + params['env'] = custom_env + p = subprocess.run(**params) return p.returncode, p.stdout.decode() if p.stdout else None diff --git a/bauh/gems/arch/__init__.py b/bauh/gems/arch/__init__.py index 5350f171..22fcb91f 100644 --- a/bauh/gems/arch/__init__.py +++ b/bauh/gems/arch/__init__.py @@ -18,6 +18,7 @@ SUGGESTIONS_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/arch/aur_suggestions.txt' UPDATES_IGNORED_FILE = '{}/updates_ignored.txt'.format(CONFIG_DIR) EDITABLE_PKGBUILDS_FILE = '{}/aur/editable_pkgbuilds.txt'.format(CONFIG_DIR) +IGNORED_REBUILD_CHECK_FILE = '{}/aur/ignored_rebuild_check.txt'.format(CONFIG_DIR) def get_icon_path() -> str: diff --git a/bauh/gems/arch/config.py b/bauh/gems/arch/config.py index 64bd733f..d1261f83 100644 --- a/bauh/gems/arch/config.py +++ b/bauh/gems/arch/config.py @@ -38,4 +38,6 @@ def get_default_config(self) -> dict: 'suggest_unneeded_uninstall': False, 'suggest_optdep_uninstall': False, 'aur_idx_exp': 1, - 'categories_exp': 24} + 'categories_exp': 24, + 'aur_rebuild_detector': True, + "aur_rebuild_detector_no_bin": True} diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index b14989aa..fa1c48db 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -26,6 +26,7 @@ ViewComponent, PanelComponent, MultipleSelectComponent, TextInputComponent, TextInputType, \ FileChooserComponent, TextComponent from bauh.api.constants import TEMP_DIR +from bauh.api.exception import NoInternetException from bauh.commons import user, system from bauh.commons.boot import CreateConfigFile from bauh.commons.category import CategoriesDownloader @@ -36,7 +37,7 @@ from bauh.gems.arch import aur, pacman, makepkg, message, confirmation, disk, git, \ gpg, URL_CATEGORIES_FILE, CATEGORIES_FILE_PATH, CUSTOM_MAKEPKG_FILE, SUGGESTIONS_FILE, \ get_icon_path, database, mirrors, sorting, cpu_manager, UPDATES_IGNORED_FILE, \ - CONFIG_DIR, EDITABLE_PKGBUILDS_FILE, URL_GPG_SERVERS, BUILD_DIR + CONFIG_DIR, EDITABLE_PKGBUILDS_FILE, URL_GPG_SERVERS, BUILD_DIR, rebuild_detector from bauh.gems.arch.aur import AURClient from bauh.gems.arch.config import get_build_dir, ArchConfigManager from bauh.gems.arch.dependencies import DependenciesAnalyser @@ -61,6 +62,7 @@ RE_PKGBUILD_PKGNAME = re.compile(r'pkgname\s*=.+') RE_CONFLICT_DETECTED = re.compile(r'\n::\s*(.+)\s+are in conflict\s*.') RE_DEPENDENCY_BREAKAGE = re.compile(r'\n?::\s+installing\s+(.+\s\(.+\))\sbreaks\sdependency\s\'(.+)\'\srequired\sby\s(.+)\s*', flags=re.IGNORECASE) +RE_PKG_ENDS_WITH_BIN = re.compile(r'.+[\-_]bin$') class TransactionContext: @@ -246,7 +248,7 @@ def __init__(self, context: ApplicationContext, disk_cache_updater: Optional[Arc self.disk_cache_updater = disk_cache_updater @staticmethod - def get_semantic_search_map() -> Dict[str, str]: + def get_aur_semantic_search_map() -> Dict[str, str]: return {'google chrome': 'google-chrome', 'chrome google': 'google-chrome', 'googlechrome': 'google-chrome'} @@ -337,61 +339,21 @@ def sync_databases(self, root_password: str, watcher: ProcessWatcher) -> bool: database.register_sync(self.logger) return True - def _upgrade_search_result(self, apidata: dict, installed_pkgs: Dict[str, ArchPackage], res: SearchResult): - pkg = installed_pkgs.get(apidata['Name']) - - if not pkg: - pkg = self.aur_mapper.map_api_data(apidata, None, self.categories) - - if pkg.installed: - res.installed.append(pkg) - else: - res.new.append(pkg) - - Thread(target=self.aur_mapper.fill_package_build, args=(pkg,), daemon=True).start() - - def _search_in_repos_and_fill(self, words: str, disk_loader: DiskCacheLoader, read_installed: Thread, installed: List[ArchPackage], res: SearchResult): - repo_search = pacman.search(words) - pkgname = words.split(' ')[0].strip() - - if not repo_search or pkgname not in repo_search: - pkg_found = pacman.get_info_dict(pkgname, remote=False) - - if pkg_found and pkg_found['validated by'] and pkg_found['name'] not in repo_search: - repo_search[pkgname] = {'version': pkg_found.get('version'), - 'repository': 'unknown', - 'description': pkg_found.get('description')} - - if repo_search: - repo_pkgs = [] - for name, data in repo_search.items(): - pkg = ArchPackage(name=name, i18n=self.i18n, **data) - repo_pkgs.append(pkg) - - if repo_pkgs: - read_installed.join() - - repo_installed = {p.name: p for p in installed if p.repository != 'aur'} if installed else {} - - for pkg in repo_pkgs: - pkg_installed = repo_installed.get(pkg.name) - if pkg_installed: - res.installed.append(pkg_installed) - else: - pkg.installed = False - res.new.append(pkg) + def _fill_repos_search_results(self, query: str, output: dict): + ti = time.time() + output['repositories'] = pacman.search(query) + tf = time.time() + self.logger.info("Repositories search took {0:.2f} seconds".format(tf - ti)) - def _search_in_aur_and_fill(self, words: str, disk_loader: DiskCacheLoader, read_installed: Thread, installed: List[ArchPackage], res: SearchResult): - api_res = self.aur_client.search(words) + def _fill_aur_search_results(self, query: str, output: dict): + ti = time.time() + api_res = self.aur_client.search(query) + pkgs_found = None if api_res and api_res.get('results'): - read_installed.join() - aur_installed = {p.name: p for p in installed if p.repository == 'aur'} - - for pkgdata in api_res['results']: - self._upgrade_search_result(pkgdata, aur_installed, res) - - else: # if there are no results from the API (it could be because there were too many), tries the names index: + pkgs_found = api_res['results'] + else: + tii = time.time() if self.index_aur: self.index_aur.join() @@ -400,103 +362,157 @@ def _search_in_aur_and_fill(self, words: str, disk_loader: DiskCacheLoader, read self.logger.info("Querying through the local AUR index") to_query = set() for norm_name, real_name in aur_index.items(): - if words in norm_name: + if query in norm_name: to_query.add(real_name) if len(to_query) == 25: break - pkgsinfo = self.aur_client.get_info(to_query) + pkgs_found = self.aur_client.get_info(to_query) + + tif = time.time() + self.logger.info("Query through local AUR index took {0:.2f} seconds".format(tif - tii)) + + if pkgs_found: + for pkg in pkgs_found: + output['aur'][pkg['Name']] = pkg + + tf = time.time() + self.logger.info("AUR search took {0:.2f} seconds".format(tf - ti)) - if pkgsinfo: - read_installed.join() - aur_installed = {p.name: p for p in installed if p.repository == 'aur'} + def __fill_search_installed_and_matched(self, query: str, res: dict): + matches = set() + installed = pacman.list_installed_names() + res['installed'] = installed + res['installed_matches'] = matches - for pkgdata in pkgsinfo: - self._upgrade_search_result(pkgdata, aur_installed, res) + if installed and ' ' not in query: # already filling some matches only based on the query + matches.update((name for name in installed if query in name)) def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult: if is_url: - return SearchResult([], [], 0) + return SearchResult.empty() arch_config = self.configman.get_config() - aur_supported = aur.is_supported(arch_config) - - if not any([arch_config['repositories'], aur_supported]): - return SearchResult([], [], 0) + repos_supported, aur_supported = arch_config['repositories'], aur.is_supported(arch_config) - installed = [] - read_installed = Thread(target=lambda: installed.extend(self.read_installed(disk_loader=disk_loader, - only_apps=False, - limit=-1, - internet_available=True).installed), daemon=True) - read_installed.start() - - res = SearchResult([], [], 0) + if not any([repos_supported, aur_supported]): + return SearchResult.empty() - if not any([aur_supported, arch_config['repositories']]): - return res + res = SearchResult.empty() - mapped_words = self.get_semantic_search_map().get(words) - final_words = mapped_words or words + search_output, search_threads = {'aur': {}, 'repositories': {}}, [] + t = Thread(target=self.__fill_search_installed_and_matched, args=(words, search_output), daemon=True) + t.start() + search_threads.append(t) - aur_search = None if aur_supported: - aur_search = Thread(target=self._search_in_aur_and_fill, args=(final_words, disk_loader, read_installed, installed, res), daemon=True) - aur_search.start() + aur_query = self.get_aur_semantic_search_map().get(words, words) + taur = Thread(target=self._fill_aur_search_results, args=(aur_query, search_output), daemon=True) + taur.start() + search_threads.append(taur) - if arch_config['repositories']: - self._search_in_repos_and_fill(final_words, disk_loader, read_installed, installed, res) + if repos_supported: + trepo = Thread(target=self._fill_repos_search_results, args=(words, search_output), daemon=True) + trepo.start() + search_threads.append(trepo) - if aur_search: - aur_search.join() + for t in search_threads: + t.join() - res.total = len(res.installed) + len(res.new) + for name in {*search_output['repositories'].keys(), *search_output['aur'].keys()}: + if name in search_output['installed']: + search_output['installed_matches'].add(name) + + if search_output['installed_matches']: + installed = self.read_installed(disk_loader=disk_loader, names=search_output['installed_matches']).installed + + for pkg in installed: + if pkg.repository != 'aur': + if repos_supported: + res.installed.append(pkg) + if pkg.name in search_output['repositories']: + del search_output['repositories'][pkg.name] + elif aur_supported: + res.installed.append(pkg) + if pkg.name in search_output['aur']: + del search_output['aur'][pkg.name] + + if search_output['repositories']: + for pkgname, data in search_output['repositories'].items(): + res.new.append(ArchPackage(name=pkgname, i18n=self.i18n, **data)) + + if search_output['aur']: + for pkgname, apidata in search_output['aur'].items(): + res.new.append(self.aur_mapper.map_api_data(apidata, None, self.categories)) + + res.update_total() return res def _fill_aur_pkgs(self, aur_pkgs: dict, output: List[ArchPackage], disk_loader: DiskCacheLoader, internet_available: bool, - arch_config: dict): + arch_config: dict, rebuild_check: Optional[Thread], rebuild_ignored: Optional[Thread], rebuild_output: Optional[Dict[str, Set[str]]]): if internet_available: try: pkgsinfo = self.aur_client.get_info(aur_pkgs.keys()) + except requests.exceptions.ConnectionError: + self.logger.warning('Could not retrieve installed AUR packages API data. It seems the internet connection is off.') + self.logger.info("Reading only local AUR packages data") + return - if pkgsinfo: - editable_pkgbuilds = self._read_editable_pkgbuilds() if arch_config['edit_aur_pkgbuild'] is not False else None - for pkgdata in pkgsinfo: - pkg = self.aur_mapper.map_api_data(pkgdata, aur_pkgs, self.categories) - pkg.pkgbuild_editable = pkg.name in editable_pkgbuilds if editable_pkgbuilds is not None else None + if pkgsinfo: + editable_pkgbuilds = self._read_editable_pkgbuilds() if arch_config['edit_aur_pkgbuild'] is not False else None - if pkg.installed: - if disk_loader: - disk_loader.fill(pkg, sync=True) + ignore_rebuild_check = None + if rebuild_ignored and rebuild_output is not None: + rebuild_ignored.join() + ignore_rebuild_check = rebuild_output['ignored'] - pkg.update = self._check_aur_package_update(pkg=pkg, - installed_data=aur_pkgs.get(pkg.name, {}), - api_data=pkgdata) - pkg.status = PackageStatus.READY - output.append(pkg) + to_rebuild = None + if rebuild_check and rebuild_output is not None: + self.logger.info("Waiting for rebuild-detector") + rebuild_check.join() + to_rebuild = rebuild_output['to_rebuild'] - return + for pkgdata in pkgsinfo: + pkg = self.aur_mapper.map_api_data(pkgdata, aur_pkgs, self.categories) + pkg.pkgbuild_editable = pkg.name in editable_pkgbuilds if editable_pkgbuilds is not None else None - except requests.exceptions.ConnectionError: - self.logger.warning('Could not retrieve installed AUR packages API data. It seems the internet connection is off.') - self.logger.info("Reading only local AUR packages data") + if pkg.installed: + if disk_loader: + disk_loader.fill(pkg, sync=True) - editable_pkgbuilds = self._read_editable_pkgbuilds() if arch_config['edit_aur_pkgbuild'] is not False else None - for name, data in aur_pkgs.items(): - pkg = ArchPackage(name=name, version=data.get('version'), - latest_version=data.get('version'), description=data.get('description'), - installed=True, repository='aur', i18n=self.i18n) + pkg.update = self._check_aur_package_update(pkg=pkg, + installed_data=aur_pkgs.get(pkg.name, {}), + api_data=pkgdata) + pkg.aur_update = pkg.update # used in 'set_rebuild_check' - pkg.categories = self.categories.get(pkg.name) - pkg.pkgbuild_editable = pkg.name in editable_pkgbuilds if editable_pkgbuilds is not None else None + if ignore_rebuild_check is not None: + pkg.allow_rebuild = pkg.name not in ignore_rebuild_check - if disk_loader: - disk_loader.fill(pkg) + if to_rebuild and not pkg.update and pkg.name in to_rebuild: + pkg.require_rebuild = True + + pkg.update_state() + + pkg.status = PackageStatus.READY + output.append(pkg) + + else: + editable_pkgbuilds = self._read_editable_pkgbuilds() if arch_config['edit_aur_pkgbuild'] is not False else None + for name, data in aur_pkgs.items(): + pkg = ArchPackage(name=name, version=data.get('version'), + latest_version=data.get('version'), description=data.get('description'), + installed=True, repository='aur', i18n=self.i18n) + + pkg.categories = self.categories.get(pkg.name) + pkg.pkgbuild_editable = pkg.name in editable_pkgbuilds if editable_pkgbuilds is not None else None + + if disk_loader: + disk_loader.fill(pkg) - pkg.status = PackageStatus.READY - output.append(pkg) + pkg.status = PackageStatus.READY + output.append(pkg) def _check_aur_package_update(self, pkg: ArchPackage, installed_data: dict, api_data: dict) -> bool: if pkg.last_modified is None: # if last_modified is not available, then the install_date will be used instead @@ -515,18 +531,15 @@ def _check_aur_package_update(self, pkg: ArchPackage, installed_data: dict, api_ def _fill_repo_updates(self, updates: dict): updates.update(pacman.list_repository_updates()) - def _fill_repo_pkgs(self, repo_pkgs: dict, pkgs: list, aur_supported: bool, disk_loader: DiskCacheLoader): + def _fill_repo_pkgs(self, repo_pkgs: dict, pkgs: list, aur_index: Optional[Set[str]], disk_loader: DiskCacheLoader): updates = {} thread_updates = Thread(target=self._fill_repo_updates, args=(updates,), daemon=True) thread_updates.start() repo_map = pacman.map_repositories(repo_pkgs) - if len(repo_map) != len(repo_pkgs): - self.logger.warning("Not mapped all signed packages repositories. Mapped: {}. Total: {}".format(len(repo_map), len(repo_pkgs))) thread_updates.join() - self.logger.info("Repository updates found" if updates else "No repository updates found") for name, data in repo_pkgs.items(): @@ -540,7 +553,7 @@ def _fill_repo_pkgs(self, repo_pkgs: dict, pkgs: list, aur_supported: bool, disk i18n=self.i18n, installed=True, repository=pkgrepo, - categories=self.categories.get(name)) + categories=self.categories.get(name, [])) if updates: update_version = updates.get(pkg.name) @@ -552,8 +565,16 @@ def _fill_repo_pkgs(self, repo_pkgs: dict, pkgs: list, aur_supported: bool, disk if disk_loader: disk_loader.fill(pkg, sync=True) - if aur_supported or pkg.repository != 'aur': # this case happens when a package was removed from AUR - pkgs.append(pkg) + if pkg.repository == 'aur': + pkg.repository = None + + if aur_index and pkg.name not in aur_index: + removed_cat = self.i18n['arch.category.remove_from_aur'] + + if removed_cat not in pkg.categories: + pkg.categories.append(removed_cat) + + pkgs.append(pkg) def _wait_for_disk_cache(self): if self.disk_cache_updater and self.disk_cache_updater.is_alive(): @@ -561,34 +582,65 @@ def _wait_for_disk_cache(self): self.disk_cache_updater.join() self.logger.info("Disk cache ready") + def __fill_packages_to_rebuild(self, output: Dict[str, Set[str]], ignore_binaries: bool): + if rebuild_detector.is_installed(): + self.logger.info("rebuild-detector: checking") + to_rebuild = rebuild_detector.list_required_rebuild() + + if to_rebuild and ignore_binaries: + to_rebuild = {p for p in to_rebuild if not RE_PKG_ENDS_WITH_BIN.match(p)} + + output['to_rebuild'].update(to_rebuild) + self.logger.info("rebuild-detector: {} packages require rebuild".format(len(to_rebuild))) + + def __fill_ignored_by_rebuild_detector(self, output: Dict[str, Set[str]]): + output['ignored'].update(rebuild_detector.list_ignored()) + 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, names: Iterable[str] = None, wait_disk_cache: bool = True) -> SearchResult: self.aur_client.clean_caches() arch_config = self.configman.get_config() - aur_supported = aur.is_supported(arch_config) + aur_supported, repos_supported = aur.is_supported(arch_config), arch_config['repositories'] + + if not aur_supported and not repos_supported: + return SearchResult.empty() + + rebuild_output, rebuild_check, rebuild_ignored = None, None, None + if aur_supported and arch_config['aur_rebuild_detector']: + rebuild_output = {'to_rebuild': set(), 'ignored': set()} + rebuild_check = Thread(target=self.__fill_packages_to_rebuild, + args=(rebuild_output, arch_config['aur_rebuild_detector_no_bin']), + daemon=True) + rebuild_check.start() + + rebuild_ignored = Thread(target=self.__fill_ignored_by_rebuild_detector, args=(rebuild_output, ), daemon=True) + rebuild_ignored.start() installed = pacman.map_installed(names=names) - aur_pkgs, repo_pkgs = None, None + aur_pkgs, repo_pkgs, aur_index = None, None, None - if arch_config['repositories'] and installed['signed']: + if repos_supported: repo_pkgs = installed['signed'] if installed['not_signed']: - if self.index_aur: - self.index_aur.join() + if aur_supported: + if self.index_aur: + self.index_aur.join() - aur_index = self.aur_client.read_index() + aur_index = self.aur_client.read_index() - for pkg in {*installed['not_signed']}: - if pkg not in aur_index: - if repo_pkgs is not None: - repo_pkgs[pkg] = installed['not_signed'][pkg] + for pkg in {*installed['not_signed']}: + if pkg not in aur_index: + if repos_supported: + repo_pkgs[pkg] = installed['not_signed'][pkg] - if aur_supported and installed['not_signed']: - del installed['not_signed'][pkg] + if aur_supported and installed['not_signed']: + del installed['not_signed'][pkg] - aur_pkgs = installed['not_signed'] if aur_supported else None + aur_pkgs = installed['not_signed'] + elif repos_supported: + repo_pkgs.update(installed['not_signed']) pkgs = [] if repo_pkgs or aur_pkgs: @@ -598,12 +650,12 @@ def read_installed(self, disk_loader: Optional[DiskCacheLoader], limit: int = -1 map_threads = [] if aur_pkgs: - t = Thread(target=self._fill_aur_pkgs, args=(aur_pkgs, pkgs, disk_loader, internet_available, arch_config), daemon=True) + t = Thread(target=self._fill_aur_pkgs, args=(aur_pkgs, pkgs, disk_loader, internet_available, arch_config, rebuild_check, rebuild_ignored, rebuild_output), daemon=True) t.start() map_threads.append(t) if repo_pkgs: - t = Thread(target=self._fill_repo_pkgs, args=(repo_pkgs, pkgs, aur_supported, disk_loader), daemon=True) + t = Thread(target=self._fill_repo_pkgs, args=(repo_pkgs, pkgs, aur_index, disk_loader), daemon=True) t.start() map_threads.append(t) @@ -1412,26 +1464,28 @@ def get_managed_types(self) -> Set["type"]: return {ArchPackage} def _get_info_aur_pkg(self, pkg: ArchPackage) -> dict: - if pkg.installed: - t = Thread(target=self.aur_mapper.fill_package_build, args=(pkg,), daemon=True) - t.start() + fill_pkgbuild = Thread(target=self.aur_mapper.fill_package_build, args=(pkg,), daemon=True) + fill_pkgbuild.start() + if pkg.installed: info = pacman.get_info_dict(pkg.name) - self._parse_dates_string_from_info(pkg.name, info) - if pkg.commit: - info['commit'] = pkg.commit + if info is not None: + self._parse_dates_string_from_info(pkg.name, info) - if pkg.last_modified: - info['last_modified'] = self._parse_timestamp(ts=pkg.last_modified, - error_msg="Could not parse AUR package '{}' 'last_modified' field ({})".format(pkg.name, pkg.last_modified)) + if pkg.commit: + info['commit'] = pkg.commit - t.join() + if pkg.last_modified: + info['last_modified'] = self._parse_timestamp(ts=pkg.last_modified, + error_msg="Could not parse AUR package '{}' 'last_modified' field ({})".format(pkg.name, pkg.last_modified)) - if pkg.pkgbuild: - info['13_pkg_build'] = pkg.pkgbuild + info['14_installed_files'] = pacman.list_installed_files(pkg.name) + + fill_pkgbuild.join() - info['14_installed_files'] = pacman.list_installed_files(pkg.name) + if pkg.pkgbuild: + info['13_pkg_build'] = pkg.pkgbuild return info else: @@ -1474,6 +1528,8 @@ def _get_info_aur_pkg(self, pkg: ArchPackage) -> dict: else: info[info_attr].extend(srcinfo[arch_attr]) + fill_pkgbuild.join() + if pkg.pkgbuild: info['00_pkg_build'] = pkg.pkgbuild else: @@ -1501,9 +1557,12 @@ def _parse_timestamp(self, ts: int, error_msg: str) -> datetime: def _get_info_repo_pkg(self, pkg: ArchPackage) -> dict: info = pacman.get_info_dict(pkg.name, remote=not pkg.installed) - self._parse_dates_string_from_info(pkg.name, info) - if pkg.installed: - info['installed files'] = pacman.list_installed_files(pkg.name) + + if info is not None: + self._parse_dates_string_from_info(pkg.name, info) + + if pkg.installed: + info['installed files'] = pacman.list_installed_files(pkg.name) return info @@ -2793,6 +2852,22 @@ def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent: label_params=['(AUR)'], capitalize_label=False, max_width=max_width), + self._gen_bool_selector(id_='rebuild_detector', + label_key='arch.config.aur_rebuild_detector', + tooltip_key='arch.config.aur_rebuild_detector.tip', + value=bool(arch_config['aur_rebuild_detector']), + label_params=['(AUR)'], + tooltip_params=["'rebuild-detector'"], + capitalize_label=False, + max_width=max_width), + self._gen_bool_selector(id_='rebuild_detector_no_bin', + label_key='arch.config.aur_rebuild_detector_no_bin', + label_params=['rebuild-detector'], + tooltip_key='arch.config.aur_rebuild_detector_no_bin.tip', + tooltip_params=['rebuild-detector', self.i18n['arch.config.aur_rebuild_detector'].format('')], + value=bool(arch_config['aur_rebuild_detector_no_bin']), + capitalize_label=False, + max_width=max_width), self._gen_bool_selector(id_='autoprovs', label_key='arch.config.automatch_providers', tooltip_key='arch.config.automatch_providers.tip', @@ -2893,35 +2968,43 @@ def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent: value=arch_config['categories_exp'] if isinstance(arch_config['categories_exp'], int) else ''), ] - return PanelComponent([FormComponent(fields, spaces=False)]) + return PanelComponent([FormComponent(fields, spaces=False, id_='root')]) def save_settings(self, component: PanelComponent) -> Tuple[bool, Optional[List[str]]]: arch_config = self.configman.get_config() - panel = component.components[0] - arch_config['repositories'] = panel.get_component('repos').get_selected() - arch_config['aur'] = panel.get_component('aur').get_selected() - arch_config['optimize'] = panel.get_component('opts').get_selected() - arch_config['sync_databases'] = panel.get_component('sync_dbs').get_selected() - arch_config['sync_databases_startup'] = panel.get_component('sync_dbs_start').get_selected() - arch_config['clean_cached'] = panel.get_component('clean_cached').get_selected() - arch_config['refresh_mirrors_startup'] = panel.get_component('ref_mirs').get_selected() - arch_config['mirrors_sort_limit'] = panel.get_component('mirrors_sort_limit').get_int_value() - arch_config['repositories_mthread_download'] = panel.get_component('mthread_download').get_selected() - arch_config['automatch_providers'] = panel.get_component('autoprovs').get_selected() - arch_config['edit_aur_pkgbuild'] = panel.get_component('edit_aur_pkgbuild').get_selected() - arch_config['aur_remove_build_dir'] = panel.get_component('aur_remove_build_dir').get_selected() - arch_config['aur_build_dir'] = panel.get_component('aur_build_dir').file_path - arch_config['aur_build_only_chosen'] = panel.get_component('aur_build_only_chosen').get_selected() - arch_config['aur_idx_exp'] = panel.get_component('aur_idx_exp').get_int_value() - arch_config['check_dependency_breakage'] = panel.get_component('check_dependency_breakage').get_selected() - arch_config['suggest_optdep_uninstall'] = panel.get_component('suggest_optdep_uninstall').get_selected() - arch_config['suggest_unneeded_uninstall'] = panel.get_component('suggest_unneeded_uninstall').get_selected() - arch_config['categories_exp'] = panel.get_component('arch_cats_exp').get_int_value() + form = component.get_form_component('root') + arch_config['repositories'] = form.get_single_select_component('repos').get_selected() + arch_config['optimize'] = form.get_single_select_component('opts').get_selected() + arch_config['aur_rebuild_detector'] = form.get_single_select_component('rebuild_detector').get_selected() + arch_config['aur_rebuild_detector_no_bin'] = form.get_single_select_component('rebuild_detector_no_bin').get_selected() + arch_config['sync_databases'] = form.get_single_select_component('sync_dbs').get_selected() + arch_config['sync_databases_startup'] = form.get_single_select_component('sync_dbs_start').get_selected() + arch_config['clean_cached'] = form.get_single_select_component('clean_cached').get_selected() + arch_config['refresh_mirrors_startup'] = form.get_single_select_component('ref_mirs').get_selected() + arch_config['mirrors_sort_limit'] = form.get_component('mirrors_sort_limit').get_int_value() + arch_config['repositories_mthread_download'] = form.get_component('mthread_download').get_selected() + arch_config['automatch_providers'] = form.get_single_select_component('autoprovs').get_selected() + arch_config['edit_aur_pkgbuild'] = form.get_single_select_component('edit_aur_pkgbuild').get_selected() + arch_config['aur_remove_build_dir'] = form.get_single_select_component('aur_remove_build_dir').get_selected() + arch_config['aur_build_dir'] = form.get_component('aur_build_dir').file_path + arch_config['aur_build_only_chosen'] = form.get_single_select_component('aur_build_only_chosen').get_selected() + arch_config['aur_idx_exp'] = form.get_component('aur_idx_exp').get_int_value() + arch_config['check_dependency_breakage'] = form.get_single_select_component('check_dependency_breakage').get_selected() + arch_config['suggest_optdep_uninstall'] = form.get_single_select_component('suggest_optdep_uninstall').get_selected() + arch_config['suggest_unneeded_uninstall'] = form.get_single_select_component('suggest_unneeded_uninstall').get_selected() + arch_config['categories_exp'] = form.get_component('arch_cats_exp').get_int_value() if not arch_config['aur_build_dir']: arch_config['aur_build_dir'] = None + aur_enabled_select = form.get_single_select_component('aur') + arch_config['aur'] = aur_enabled_select.get_selected() + + if aur_enabled_select.changed() and arch_config['aur']: + self.index_aur = AURIndexUpdater(context=self.context, taskman=TaskManager(), arch_config=arch_config) + self.index_aur.start() + try: self.configman.save_config(arch_config) return True, None @@ -3386,3 +3469,51 @@ def _list_opt_deps_with_no_hard_requirements(self, source_pkgs: Set[str], instal continue return res + + def reinstall(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatcher) -> bool: # only available for AUR packages + if not self.context.internet_checker.is_available(): + raise NoInternetException() + + self.aur_client.clean_caches() + + apidatas = self.aur_client.get_info({pkg.name}) + + if not apidatas: + watcher.show_message(title=self.i18n['error'], + body=self.i18n['arch.action.reinstall.error.no_apidata'], + type_=MessageType.ERROR) + return False + + self.aur_mapper.fill_last_modified(pkg, apidatas[0]) + context = TransactionContext.gen_context_from(pkg=pkg, + arch_config=self.configman.get_config(), + root_password=root_password, + handler=ProcessHandler(watcher), + aur_supported=True) + context.skip_opt_deps = False + context.update_aur_index = True + + return self.install(pkg=pkg, + root_password=root_password, + watcher=watcher, + context=context, + disk_loader=self.context.disk_loader_factory.new()).success + + def set_rebuild_check(self, pkg: ArchPackage, root_password: str, watcher: ProcessWatcher) -> bool: + if pkg.repository != 'aur': + return False + + try: + if pkg.allow_rebuild: + rebuild_detector.add_as_ignored(pkg.name) + pkg.allow_rebuild = False + else: + rebuild_detector.remove_from_ignored(pkg.name) + pkg.allow_rebuild = True + except: + self.logger.error("An unexpected exception happened") + traceback.print_exc() + return False + + pkg.update_state() + return True diff --git a/bauh/gems/arch/mapper.py b/bauh/gems/arch/mapper.py index 6888985c..0dc1dc9d 100644 --- a/bauh/gems/arch/mapper.py +++ b/bauh/gems/arch/mapper.py @@ -89,7 +89,7 @@ def check_version_update(version: str, latest_version: str) -> bool: def fill_package_build(self, pkg: ArchPackage): cached_pkgbuild = pkg.get_cached_pkgbuild_path() - if os.path.exists(cached_pkgbuild): + if pkg.installed and os.path.exists(cached_pkgbuild): with open(cached_pkgbuild) as f: pkg.pkgbuild = f.read() else: diff --git a/bauh/gems/arch/model.py b/bauh/gems/arch/model.py index 563d38c4..f63c26d4 100644 --- a/bauh/gems/arch/model.py +++ b/bauh/gems/arch/model.py @@ -7,19 +7,40 @@ CACHED_ATTRS = {'command', 'icon_path', 'repository', 'maintainer', 'desktop_entry', 'categories', 'last_modified', 'commit'} -ACTIONS_AUR_ENABLE_PKGBUILD_EDITION = [CustomSoftwareAction(i18n_label_key='arch.action.enable_pkgbuild_edition', - i18n_status_key='arch.action.enable_pkgbuild_edition.status', - i18n_confirm_key='arch.action.enable_pkgbuild_edition.confirm', - requires_root=False, - manager_method='enable_pkgbuild_edition', - icon_path=resource.get_path('img/mark_pkgbuild.svg', ROOT_DIR))] - -ACTIONS_AUR_DISABLE_PKGBUILD_EDITION = [CustomSoftwareAction(i18n_label_key='arch.action.disable_pkgbuild_edition', - i18n_status_key='arch.action.disable_pkgbuild_edition', - i18n_confirm_key='arch.action.disable_pkgbuild_edition.confirm', - requires_root=False, - manager_method='disable_pkgbuild_edition', - icon_path=resource.get_path('img/unmark_pkgbuild.svg', ROOT_DIR))] +ACTION_AUR_ENABLE_PKGBUILD_EDITION = CustomSoftwareAction(i18n_label_key='arch.action.enable_pkgbuild_edition', + i18n_status_key='arch.action.enable_pkgbuild_edition.status', + i18n_confirm_key='arch.action.enable_pkgbuild_edition.confirm', + requires_root=False, + manager_method='enable_pkgbuild_edition', + icon_path=resource.get_path('img/mark_pkgbuild.svg', ROOT_DIR)) + +ACTION_AUR_DISABLE_PKGBUILD_EDITION = CustomSoftwareAction(i18n_label_key='arch.action.disable_pkgbuild_edition', + i18n_status_key='arch.action.disable_pkgbuild_edition', + i18n_confirm_key='arch.action.disable_pkgbuild_edition.confirm', + requires_root=False, + manager_method='disable_pkgbuild_edition', + icon_path=resource.get_path('img/unmark_pkgbuild.svg', ROOT_DIR)) + +ACTION_AUR_REINSTALL = CustomSoftwareAction(i18n_label_key='arch.action.reinstall', + i18n_status_key='arch.action.reinstall.status', + i18n_confirm_key='arch.action.reinstall.confirm', + requires_root=True, + manager_method='reinstall', + icon_path=resource.get_path('img/build.svg', ROOT_DIR)) + +ACTION_IGNORE_REBUILD_CHECK = CustomSoftwareAction(i18n_label_key='arch.action.rebuild_check.ignore', + i18n_status_key='arch.action.rebuild_check.ignore.status', + i18n_confirm_key='arch.action.rebuild_check.ignore.confirm', + requires_root=False, + manager_method='set_rebuild_check', + icon_path=resource.get_path('img/check_disabled.svg', ROOT_DIR)) + +ACTION_ALLOW_REBUILD_CHECK = CustomSoftwareAction(i18n_label_key='arch.action.rebuild_check.allow', + i18n_status_key='arch.action.rebuild_check.allow.status', + i18n_confirm_key='arch.action.rebuild_check.allow.confirm', + requires_root=False, + manager_method='set_rebuild_check', + icon_path=resource.get_path('img/check.svg', ROOT_DIR)) class ArchPackage(SoftwarePackage): @@ -30,7 +51,10 @@ def __init__(self, name: str = None, version: str = None, latest_version: str = maintainer: str = None, url_download: str = None, pkgbuild: str = None, repository: str = None, desktop_entry: str = None, installed: bool = False, srcinfo: dict = None, dependencies: Set[str] = None, categories: List[str] = None, i18n: I18n = None, update_ignored: bool = False, arch: str = None, - pkgbuild_editable: bool = None, install_date: Optional[int] = None, commit: Optional[str] = None): + pkgbuild_editable: bool = None, install_date: Optional[int] = None, commit: Optional[str] = None, + require_rebuild: bool = False, + allow_rebuild: Optional[bool] = None, + aur_update: bool = False): super(ArchPackage, self).__init__(name=name, version=version, latest_version=latest_version, description=description, installed=installed, categories=categories) @@ -55,6 +79,9 @@ def __init__(self, name: str = None, version: str = None, latest_version: str = self.pkgbuild_editable = pkgbuild_editable # if the PKGBUILD can be edited by the user (only for AUR) self.install_date = install_date self.commit = commit # only for AUR for downgrading purposes + self.require_rebuild = require_rebuild + self.allow_rebuild = allow_rebuild + self.aur_update = aur_update @staticmethod def disk_cache_path(pkgname: str): @@ -174,11 +201,27 @@ def get_cached_pkgbuild_path(self) -> str: return '{}/PKGBUILD'.format(self.get_disk_cache_path()) def get_custom_supported_actions(self) -> List[CustomSoftwareAction]: - if self.installed and self.pkgbuild_editable is not None and self.repository == 'aur': - if self.pkgbuild_editable: - return ACTIONS_AUR_DISABLE_PKGBUILD_EDITION + if self.installed and self.repository == 'aur': + actions = [ACTION_AUR_REINSTALL] + + if self.pkgbuild_editable is not None: + actions.append(ACTION_AUR_DISABLE_PKGBUILD_EDITION if self.pkgbuild_editable else ACTION_AUR_ENABLE_PKGBUILD_EDITION) + + if self.allow_rebuild is not None: + actions.append(ACTION_IGNORE_REBUILD_CHECK if self.allow_rebuild else ACTION_ALLOW_REBUILD_CHECK) + + return actions + + def get_update_tip(self) -> Optional[str]: + if self.repository == 'aur' and self.allow_rebuild and self.require_rebuild: + return self.i18n['arch.package.requires_rebuild'] + ' (rebuild)' + + def update_state(self): + if self.repository == 'aur': + if self.allow_rebuild and self.require_rebuild: + self.update = True else: - return ACTIONS_AUR_ENABLE_PKGBUILD_EDITION + self.update = self.aur_update def __hash__(self): if self.view_name is not None: diff --git a/bauh/gems/arch/pacman.py b/bauh/gems/arch/pacman.py index 13cadd25..e629ed7e 100644 --- a/bauh/gems/arch/pacman.py +++ b/bauh/gems/arch/pacman.py @@ -69,7 +69,7 @@ def get_info_list(pkg_name: str, remote: bool = False) -> List[tuple]: return re.findall(r'(\w+\s?\w+)\s*:\s*(.+(\n\s+.+)*)', info) -def get_info_dict(pkg_name: str, remote: bool = False) -> dict: +def get_info_dict(pkg_name: str, remote: bool = False) -> Optional[dict]: list_attrs = {'depends on', 'required by', 'conflicts with'} info_list = get_info_list(pkg_name, remote) diff --git a/bauh/gems/arch/rebuild_detector.py b/bauh/gems/arch/rebuild_detector.py new file mode 100644 index 00000000..96d2cce8 --- /dev/null +++ b/bauh/gems/arch/rebuild_detector.py @@ -0,0 +1,66 @@ +import os +from pathlib import Path +from typing import Set + +from bauh.commons import system +from bauh.gems.arch import IGNORED_REBUILD_CHECK_FILE + + +def is_installed() -> bool: + return system.execute(cmd='which checkrebuild', output=False)[0] == 0 + + +def list_required_rebuild() -> Set[str]: + code, output = system.execute(cmd='checkrebuild', shell=True, stdin=False) + + required = set() + if code == 0 and output: + for line in output.split('\n'): + line_strip = line.strip() + + if line_strip: + line_split = line_strip.split('\t') + + if len(line_split) > 1: + required.add(line_split[1]) + + return required + + +def list_ignored() -> Set[str]: + if os.path.isfile(IGNORED_REBUILD_CHECK_FILE): + with open(IGNORED_REBUILD_CHECK_FILE) as f: + ignored_str = f.read() + + return {p.strip() for p in ignored_str.split('\n') if p} + else: + return set() + + +def add_as_ignored(pkgname: str): + ignored = list_ignored() + ignored.add(pkgname) + + Path(os.path.dirname(IGNORED_REBUILD_CHECK_FILE)).mkdir(parents=True, exist_ok=True) + + with open(IGNORED_REBUILD_CHECK_FILE, 'w+') as f: + for p in ignored: + f.write('{}\n'.format(p)) + + +def remove_from_ignored(pkgname: str): + ignored = list_ignored() + + if ignored is None or pkgname not in ignored: + return + + ignored.remove(pkgname) + + Path(os.path.dirname(IGNORED_REBUILD_CHECK_FILE)).mkdir(parents=True, exist_ok=True) + + if ignored: + with open(IGNORED_REBUILD_CHECK_FILE, 'w+') as f: + for p in ignored: + f.write('{}\n'.format(p)) + else: + os.remove(IGNORED_REBUILD_CHECK_FILE) diff --git a/bauh/gems/arch/resources/img/build.svg b/bauh/gems/arch/resources/img/build.svg new file mode 100644 index 00000000..00ef6f52 --- /dev/null +++ b/bauh/gems/arch/resources/img/build.svg @@ -0,0 +1,119 @@ + + diff --git a/bauh/gems/arch/resources/img/check.svg b/bauh/gems/arch/resources/img/check.svg new file mode 100644 index 00000000..991670c3 --- /dev/null +++ b/bauh/gems/arch/resources/img/check.svg @@ -0,0 +1,63 @@ + + diff --git a/bauh/gems/arch/resources/img/check_disabled.svg b/bauh/gems/arch/resources/img/check_disabled.svg new file mode 100644 index 00000000..d25f6602 --- /dev/null +++ b/bauh/gems/arch/resources/img/check_disabled.svg @@ -0,0 +1,70 @@ + + diff --git a/bauh/gems/arch/resources/locale/ca b/bauh/gems/arch/resources/locale/ca index e2875e4f..29ae0f2c 100644 --- a/bauh/gems/arch/resources/locale/ca +++ b/bauh/gems/arch/resources/locale/ca @@ -1,3 +1,9 @@ +arch.action.rebuild_check.allow=Allow reinstallation check +arch.action.rebuild_check.allow.status=Allowing reinstallation check +arch.action.rebuild_check.allow.confirm=Allow reinstallation check for {} ? +arch.action.rebuild_check.ignore=Ignore reinstallation check +arch.action.rebuild_check.ignore.status=Ignoring reinstallation check +arch.action.rebuild_check.ignore.confirm=Ignore reinstallation check for {} ? arch.action.db_locked.body.l1=The system's package database is locked. arch.action.db_locked.body.l2=It is necessary to unlock it to continue. arch.action.db_locked.confirmation=Unlock and continue @@ -9,6 +15,10 @@ arch.action.disable_pkgbuild_edition.status=Unmarking PKGBUILD as editable arch.action.enable_pkgbuild_edition=Mark PKGBUILD as editable arch.action.enable_pkgbuild_edition.confirm=Mark PKGBUILD of {} as editable ? arch.action.enable_pkgbuild_edition.status=Marking PKGBUILD as editable +arch.action.reinstall=Reinstall +arch.action.reinstall.status=Reinstalling +arch.action.reinstall.confirm=Do you want to reinstall {} ? +arch.action.reinstall.error.no_apidata=It was not possible to retrieve information of {} from AUR arch.aur.action.edit_pkgbuild.body=Edit the PKGBUILD file of {} before continuing ? arch.aur.install.pgp.body=Per a instal·lar {} cal rebre les claus PGP següents arch.aur.install.pgp.receive_fail=Could not receive PGP key {} @@ -31,8 +41,13 @@ arch.checking.conflicts=S’està comprovant si hi ha conflictes amb {} arch.checking.deps=S’estan comprovant les dependències de {} arch.checking.missing_deps=Verificació de les dependències que falten de {} arch.clone=S’està clonant el dipòsit {} de l’AUR +arch.category.remove_from_aur=Removed from AUR arch.config.aur=AUR packages arch.config.aur.tip=It allows to manage AUR packages. git must be installed. +arch.config.aur_rebuild_detector=Check reinstallation need {} +arch.config.aur_rebuild_detector.tip=It checks if packages built with old library versions require to be rebuilt. If a package needs to be rebuilt, it will be marked for update ({} must be installed) +arch.config.aur_rebuild_detector_no_bin=Ignore binaries ({}) +arch.config.aur_rebuild_detector_no_bin.tip=If binary packages named as "package-bin" should be ignored by {} ({}) arch.config.automatch_providers=Auto-define dependency providers arch.config.automatch_providers.tip=It automatically chooses which provider will be used for a package dependency when both names are equal. arch.config.aur_build_dir=Build directory (AUR) @@ -200,6 +215,7 @@ arch.missing_deps_found=Dependències mancants per a {} arch.mthread_downloaded.error.cache_dir=It was not possible to create the cache directory {} arch.mthread_downloaded.error.cancelled=Operation cancelled arch.optdeps.checking=S’estan comprovant les dependències opcionals de {} +arch.package.requires_rebuild=It needs to be reinstalled arch.providers=providers arch.substatus.conflicts=Checking for conflicts arch.substatus.disk_space=Checking available disk space diff --git a/bauh/gems/arch/resources/locale/de b/bauh/gems/arch/resources/locale/de index 3df21a3f..e5a30ae0 100644 --- a/bauh/gems/arch/resources/locale/de +++ b/bauh/gems/arch/resources/locale/de @@ -1,3 +1,9 @@ +arch.action.rebuild_check.allow=Allow reinstallation check +arch.action.rebuild_check.allow.status=Allowing reinstallation check +arch.action.rebuild_check.allow.confirm=Allow reinstallation check for {} ? +arch.action.rebuild_check.ignore=Ignore reinstallation check +arch.action.rebuild_check.ignore.status=Ignoring reinstallation check +arch.action.rebuild_check.ignore.confirm=Ignore reinstallation check for {} ? arch.action.db_locked.body.l1=The system's package database is locked. arch.action.db_locked.body.l2=It is necessary to unlock it to continue. arch.action.db_locked.confirmation=Unlock and continue @@ -9,6 +15,10 @@ arch.action.disable_pkgbuild_edition.status=Unmarking PKGBUILD as editable arch.action.enable_pkgbuild_edition=Mark PKGBUILD as editable arch.action.enable_pkgbuild_edition.confirm=Mark PKGBUILD of {} as editable ? arch.action.enable_pkgbuild_edition.status=Marking PKGBUILD as editable +arch.action.reinstall=Reinstall +arch.action.reinstall.status=Reinstalling +arch.action.reinstall.confirm=Do you want to reinstall {} ? +arch.action.reinstall.error.no_apidata=It was not possible to retrieve information of {} from AUR arch.aur.action.edit_pkgbuild.body=Edit the PKGBUILD file of {} before continuing ? arch.aur.install.pgp.body=Um {} zu installieren sind folgende PGP Schlüssel nötig arch.aur.install.pgp.receive_fail=PGP Schlüssel {} konnte nicht empfangen werden @@ -31,8 +41,13 @@ arch.checking.conflicts=Konflikte mit {} überprüfen arch.checking.deps={} Abhängigkeiten überprüfen arch.checking.missing_deps=Überprüfen der fehlenden Abhängigkeiten von {} arch.clone=AUR Repository {} wird kopiert +arch.category.remove_from_aur=Removed from AUR arch.config.aur=AUR packages arch.config.aur.tip=It allows to manage AUR packages. git must be installed. +arch.config.aur_rebuild_detector=Check reinstallation need {} +arch.config.aur_rebuild_detector.tip=It checks if packages built with old library versions require to be rebuilt. If a package needs to be rebuilt, it will be marked for update ({} must be installed) +arch.config.aur_rebuild_detector_no_bin=Ignore binaries ({}) +arch.config.aur_rebuild_detector_no_bin.tip=If binary packages named as "package-bin" should be ignored by {} ({}) arch.config.automatch_providers=Auto-define dependency providers arch.config.automatch_providers.tip=It automatically chooses which provider will be used for a package dependency when both names are equal. arch.config.aur_build_dir=Build directory (AUR) @@ -200,6 +215,7 @@ arch.missing_deps_found=Fehlende Abhängigkeiten für {} arch.mthread_downloaded.error.cache_dir=It was not possible to create the cache directory {} arch.mthread_downloaded.error.cancelled=Operation cancelled arch.optdeps.checking={} optionale Abhängigkeiten überprüfen +arch.package.requires_rebuild=It needs to be reinstalled arch.providers=providers arch.substatus.conflicts=Checking for conflicts arch.substatus.disk_space=Checking available disk space diff --git a/bauh/gems/arch/resources/locale/en b/bauh/gems/arch/resources/locale/en index 7531351d..6401202a 100644 --- a/bauh/gems/arch/resources/locale/en +++ b/bauh/gems/arch/resources/locale/en @@ -1,3 +1,9 @@ +arch.action.rebuild_check.allow=Allow reinstallation check +arch.action.rebuild_check.allow.status=Allowing reinstallation check +arch.action.rebuild_check.allow.confirm=Allow reinstallation check for {} ? +arch.action.rebuild_check.ignore=Ignore reinstallation check +arch.action.rebuild_check.ignore.status=Ignoring reinstallation check +arch.action.rebuild_check.ignore.confirm=Ignore reinstallation check for {} ? arch.action.db_locked.body.l1=The system's package database is locked. arch.action.db_locked.body.l2=It is necessary to unlock it to continue. arch.action.db_locked.confirmation=Unlock and continue @@ -9,6 +15,10 @@ arch.action.disable_pkgbuild_edition.status=Unmarking PKGBUILD as editable arch.action.enable_pkgbuild_edition=Mark PKGBUILD as editable arch.action.enable_pkgbuild_edition.confirm=Mark PKGBUILD of {} as editable ? arch.action.enable_pkgbuild_edition.status=Marking PKGBUILD as editable +arch.action.reinstall=Reinstall +arch.action.reinstall.status=Reinstalling +arch.action.reinstall.confirm=Do you want to reinstall {} ? +arch.action.reinstall.error.no_apidata=It was not possible to retrieve information of {} from AUR arch.aur.action.edit_pkgbuild.body=Edit the PKGBUILD file of {} before continuing ? arch.aur.install.pgp.body=To install {} is necessary to receive the following PGP keys arch.aur.install.pgp.receive_fail=Could not receive PGP key {} @@ -31,8 +41,13 @@ arch.checking.conflicts=Checking any conflicts with {} arch.checking.deps=Checking {} dependencies arch.checking.missing_deps=Verifying missing dependencies of {} arch.clone=Cloning the AUR repository {} +arch.category.remove_from_aur=Removed from AUR arch.config.aur=AUR packages arch.config.aur.tip=It allows to manage AUR packages. git must be installed. +arch.config.aur_rebuild_detector=Check reinstallation need {} +arch.config.aur_rebuild_detector.tip=It checks if packages built with old library versions require to be rebuilt. If a package needs to be rebuilt, it will be marked for update ({} must be installed) +arch.config.aur_rebuild_detector_no_bin=Ignore binaries ({}) +arch.config.aur_rebuild_detector_no_bin.tip=If binary packages named as "package-bin" should be ignored by {} ({}) arch.config.automatch_providers=Auto-define dependency providers arch.config.automatch_providers.tip=It automatically chooses which provider will be used for a package dependency when both names are equal. arch.config.aur_build_dir=Build directory (AUR) @@ -200,6 +215,7 @@ arch.missing_deps_found=Missing dependencies for {} arch.mthread_downloaded.error.cache_dir=It was not possible to create the cache directory {} arch.mthread_downloaded.error.cancelled=Operation cancelled arch.optdeps.checking=Checking {} optional dependencies +arch.package.requires_rebuild=It needs to be reinstalled arch.providers=providers arch.substatus.conflicts=Checking for conflicts arch.substatus.disk_space=Checking available disk space diff --git a/bauh/gems/arch/resources/locale/es b/bauh/gems/arch/resources/locale/es index 082a1e58..20f7dfde 100644 --- a/bauh/gems/arch/resources/locale/es +++ b/bauh/gems/arch/resources/locale/es @@ -1,3 +1,9 @@ +arch.action.rebuild_check.allow=Permitir verificación de reinstalación +arch.action.rebuild_check.allow.status=Permitindo verificación de reinstalación +arch.action.rebuild_check.allow.confirm=¿ Permitir verificación de reinstalación para {} ? +arch.action.rebuild_check.ignore=Ignorar verificación de reinstalación +arch.action.rebuild_check.ignore.status=Ignorando verificación de reinstalación +arch.action.rebuild_check.ignore.confirm=¿ Ignorar verificación de reinstalación para {} ? arch.action.db_locked.body.l1=La base de datos de paquetes del sistema está bloqueada. arch.action.db_locked.body.l2=Es necesario desbloquearla para continuar. arch.action.db_locked.confirmation=Desbloquear y continuar @@ -9,6 +15,10 @@ arch.action.disable_pkgbuild_edition.status=Desmarcando PKGBUILD como editable arch.action.enable_pkgbuild_edition=Marcar PKGBUILD como editable arch.action.enable_pkgbuild_edition.confirm=Marcar PKGBUILD de {} como editable ? arch.action.enable_pkgbuild_edition.status=Marcando PKGBUILD como editable +arch.action.reinstall=Reinstalar +arch.action.reinstall.status=Reinstalando +arch.action.reinstall.confirm=Desea reinstalar {} ? +arch.action.reinstall.error.no_apidata=No fue posible recuperar información de {} del AUR arch.aur.action.edit_pkgbuild.body=Editar el archivo PKGBUILD de {} antes de continuar ? arch.aur.install.pgp.body=Para instalar {} es necesario recibir las siguientes claves PGP arch.aur.install.pgp.receive_fail=Could not receive PGP key {} @@ -31,8 +41,13 @@ arch.checking.conflicts=Verificando se hay conflictos con {} arch.checking.deps=Verificando las dependencias de {} arch.checking.missing_deps=Verificando las dependencias faltantes de {} arch.clone=Clonando el repositorio {} de AUR +arch.category.remove_from_aur=Eliminado del AUR arch.config.aur=Paquetes de AUR arch.config.aur.tip=Permite gestionar paquetes del AUR. git debe estar instalado. +arch.config.aur_rebuild_detector=Verificar necesidad de reinstalación {} +arch.config.aur_rebuild_detector.tip=Verifica si los paquetes creados con versiones antiguas de bibliotecas necesitan ser reconstruidos. Si es necesario reconstruir un paquete, el será marcado para actualización ({} debe estar instalado) +arch.config.aur_rebuild_detector_no_bin=Ignorar binarios ({}) +arch.config.aur_rebuild_detector_no_bin.tip=Si los paquetes binarios nombrados como "paquete-bin" deben ser ignorados por {} ({}) arch.config.automatch_providers=Autodefinir proveedores de dependencia arch.config.automatch_providers.tip=Elige automáticamente qué proveedor se usará para una dependencia de paquete cuando ambos nombres son iguales. arch.config.aur_build_dir=Directorio de compilación (AUR) @@ -200,6 +215,7 @@ arch.missing_deps_found=Dependencias faltantes para {} arch.mthread_downloaded.error.cache_dir=No fue posible crear el directorio de caché {} arch.mthread_downloaded.error.cancelled=Operación cancelada arch.optdeps.checking=Verificando las dependencias opcionales de {} +arch.package.requires_rebuild=Necesita ser reinstalado arch.providers=proveedores arch.substatus.conflicts=Verificando conflictos arch.substatus.disk_space=Verificando espacio disponible en disco diff --git a/bauh/gems/arch/resources/locale/fr b/bauh/gems/arch/resources/locale/fr index 985cfca5..b779f65a 100644 --- a/bauh/gems/arch/resources/locale/fr +++ b/bauh/gems/arch/resources/locale/fr @@ -1,3 +1,9 @@ +arch.action.rebuild_check.allow=Allow reinstallation check +arch.action.rebuild_check.allow.status=Allowing reinstallation check +arch.action.rebuild_check.allow.confirm=Allow reinstallation check for {} ? +arch.action.rebuild_check.ignore=Ignore reinstallation check +arch.action.rebuild_check.ignore.status=Ignoring reinstallation check +arch.action.rebuild_check.ignore.confirm=Ignore reinstallation check for {} ? arch.action.db_locked.body.l1=La base de données de paquets du système est verrouillée. arch.action.db_locked.body.l2=Il faut la déverrouiller pour continuer. arch.action.db_locked.confirmation=Déverrouiller et continuer @@ -9,6 +15,10 @@ arch.action.disable_pkgbuild_edition.status=Décocher PKGBUILD comme éditable arch.action.enable_pkgbuild_edition=Cocher PKGBUILD comme éditable arch.action.enable_pkgbuild_edition.confirm=Cocher le PKGBUILD de {} comme éditable ? arch.action.enable_pkgbuild_edition.status=Le PKGBUILD devient éditable +arch.action.reinstall=Reinstall +arch.action.reinstall.status=Reinstalling +arch.action.reinstall.confirm=Do you want to reinstall {} ? +arch.action.reinstall.error.no_apidata=It was not possible to retrieve information of {} from AUR arch.aur.action.edit_pkgbuild.body=Editer le fichier PKGBUILD de {} avant de continuer ? arch.aur.install.pgp.body=Il faut installer {} pour recevoir les clés PGP suivantes arch.aur.install.pgp.receive_fail=Échec de la réception de la clé PGP {} @@ -31,8 +41,13 @@ arch.checking.conflicts=Vérification de conflits aved {} arch.checking.deps=Vérification des dépendances de {} arch.checking.missing_deps=Vérification des dépendances manquantes de {} arch.clone=Copie du dépôt AUR {} +arch.category.remove_from_aur=Removed from AUR arch.config.aur=Paquêts AUR arch.config.aur.tip=Permet la gestion des paquets AUR. git doit être installé. +arch.config.aur_rebuild_detector=Check reinstallation need {} +arch.config.aur_rebuild_detector.tip=It checks if packages built with old library versions require to be rebuilt. If a package needs to be rebuilt, it will be marked for update ({} must be installed) +arch.config.aur_rebuild_detector_no_bin=Ignore binaries ({}) +arch.config.aur_rebuild_detector_no_bin.tip=If binary packages named as "package-bin" should be ignored by {} ({}) arch.config.automatch_providers=Définition automatique des fournisseurs de dépendances arch.config.automatch_providers.tip=Choisit automatiquement le fournisseur de dépendances d'un paquet en cas de noms identiques arch.config.aur_build_dir=Répertoire de compilation (AUR) @@ -200,6 +215,7 @@ arch.missing_deps_found=Dépendances manquantes pour {} arch.mthread_downloaded.error.cache_dir=Impossible de créer le dossier de cache {} arch.mthread_downloaded.error.cancelled=Operation annulée arch.optdeps.checking=Verification des dépendances optionnelles de {} +arch.package.requires_rebuild=It needs to be reinstalled arch.providers=fournisseurs arch.substatus.conflicts=Verification des conflits arch.substatus.disk_space=Verification de l'espace disque disponible diff --git a/bauh/gems/arch/resources/locale/it b/bauh/gems/arch/resources/locale/it index 26beb994..442a25d8 100644 --- a/bauh/gems/arch/resources/locale/it +++ b/bauh/gems/arch/resources/locale/it @@ -1,3 +1,9 @@ +arch.action.rebuild_check.allow=Allow reinstallation check +arch.action.rebuild_check.allow.status=Allowing reinstallation check +arch.action.rebuild_check.allow.confirm=Allow reinstallation check for {} ? +arch.action.rebuild_check.ignore=Ignore reinstallation check +arch.action.rebuild_check.ignore.status=Ignoring reinstallation check +arch.action.rebuild_check.ignore.confirm=Ignore reinstallation check for {} ? arch.action.db_locked.body.l1=The system's package database is locked. arch.action.db_locked.body.l2=It is necessary to unlock it to continue. arch.action.db_locked.confirmation=Unlock and continue @@ -9,6 +15,10 @@ arch.action.disable_pkgbuild_edition.status=Unmarking PKGBUILD as editable arch.action.enable_pkgbuild_edition=Mark PKGBUILD as editable arch.action.enable_pkgbuild_edition.confirm=Mark PKGBUILD of {} as editable ? arch.action.enable_pkgbuild_edition.status=Marking PKGBUILD as editable +arch.action.reinstall=Reinstall +arch.action.reinstall.status=Reinstalling +arch.action.reinstall.confirm=Do you want to reinstall {} ? +arch.action.reinstall.error.no_apidata=It was not possible to retrieve information of {} from AUR arch.aur.action.edit_pkgbuild.body=Edit the PKGBUILD file of {} before continuing ? arch.aur.install.pgp.body=Per installare {} è necessario ricevere le seguenti chiavi PGP arch.aur.install.pgp.receive_fail=Impossibile ricevere la chiave PGP {} @@ -31,8 +41,13 @@ arch.checking.conflicts=Verifica di eventuali conflitti con {} arch.checking.deps=Verifica di {} dipendenze arch.checking.missing_deps=Verifica delle dipendenze mancanti di {} arch.clone=Clonazione del repository AUR {} +arch.category.remove_from_aur=Removed from AUR arch.config.aur=AUR packages arch.config.aur.tip=It allows to manage AUR packages. git must be installed. +arch.config.aur_rebuild_detector=Check reinstallation need {} +arch.config.aur_rebuild_detector.tip=It checks if packages built with old library versions require to be rebuilt. If a package needs to be rebuilt, it will be marked for update ({} must be installed) +arch.config.aur_rebuild_detector_no_bin=Ignore binaries ({}) +arch.config.aur_rebuild_detector_no_bin.tip=If binary packages named as "package-bin" should be ignored by {} ({}) arch.config.automatch_providers=Auto-define dependency providers arch.config.automatch_providers.tip=It automatically chooses which provider will be used for a package dependency when both names are equal. arch.config.aur_build_dir=Build directory (AUR) @@ -200,6 +215,7 @@ arch.missing_deps_found=Dipendenze mancanti per {} arch.mthread_downloaded.error.cache_dir=It was not possible to create the cache directory {} arch.mthread_downloaded.error.cancelled=Operation cancelled arch.optdeps.checking=Verifica di {} dipendenze opzionali +arch.package.requires_rebuild=It needs to be reinstalled arch.providers=providers arch.substatus.conflicts=Checking for conflicts arch.substatus.disk_space=Checking available disk space diff --git a/bauh/gems/arch/resources/locale/pt b/bauh/gems/arch/resources/locale/pt index 6e15056b..2b7f1402 100644 --- a/bauh/gems/arch/resources/locale/pt +++ b/bauh/gems/arch/resources/locale/pt @@ -1,3 +1,9 @@ +arch.action.rebuild_check.allow=Permitir verificação de reinstalação +arch.action.rebuild_check.allow.status=Permitindo verificação de reinstalação +arch.action.rebuild_check.allow.confirm=Permitir verificação de reinstalação para {} ? +arch.action.rebuild_check.ignore=Ignorar verificação de reinstalação +arch.action.rebuild_check.ignore.status=Ignorando verificação de reinstalação +arch.action.rebuild_check.ignore.confirm=Ignorar verificação de reinstalação para {} ? arch.action.db_locked.body.l1=O banco de dados de pacotes do sistema está bloqueado. arch.action.db_locked.body.l2=É necessário desbloquea-lo para continuar. arch.action.db_locked.confirmation=Desbloquear e continuar @@ -9,6 +15,10 @@ arch.action.disable_pkgbuild_edition.status=Desmarcando PKGBUILD como editável arch.action.enable_pkgbuild_edition=Marcando PKGBUILD como editável arch.action.enable_pkgbuild_edition.confirm=Marcar o PKGBUILD de {} como editável ? arch.action.enable_pkgbuild_edition.status=Marcando PKGBUILD como editável +arch.action.reinstall=Reinstalar +arch.action.reinstall.status=Reinstalando +arch.action.reinstall.confirm=Deseja reinstalar {} ? +arch.action.reinstall.error.no_apidata=Não foi possível obter informações de {} do AUR arch.aur.action.edit_pkgbuild.body=Editar o arquivo PKGBUILD de {} antes de continuar ? arch.aur.install.pgp.body=Para instalar {} é necessário receber as seguintes chaves PGP arch.aur.install.pgp.receive_fail=Não foi possível receber a chave PGP {} @@ -31,8 +41,13 @@ arch.checking.conflicts=Verificando se há conflitos com {} arch.checking.deps=Verificando as dependências de {} arch.checking.missing_deps=Verificando dependências ausentes de {} arch.clone=Clonando o repositório {} do AUR +arch.category.remove_from_aur=Removido do AUR arch.config.aur=Pacotes do AUR arch.config.aur.tip=Permite gerenciar pacotes dos AUR. git precisa estar instalado. +arch.config.aur_rebuild_detector=Verificar necessidade de reinstalação {} +arch.config.aur_rebuild_detector.tip=Verifica se pacotes construídos com versões antigas de bibliotecas precisam ser reconstruídas. Se um pacote precisa ser reconstruído, ele será marcado para atualizar ({} precisa estar instalado). +arch.config.aur_rebuild_detector_no_bin=Ignorar binários ({}) +arch.config.aur_rebuild_detector_no_bin.tip=Se pacotes binários nomeados como "pacote-bin" devem ser ignorados pelo {} ({}) arch.config.automatch_providers=Auto-definir provedores de dependências arch.config.automatch_providers.tip=Escolhe automaticamente qual provedor será utilizado para determinada dependência de um pacote caso os nomes de ambos sejam iguais. arch.config.aur_build_dir=Diretório de construção (AUR) @@ -199,6 +214,7 @@ arch.missing_deps_found=Dependencias ausentes para {} arch.mthread_downloaded.error.cache_dir=Não foi possível criar o diretório para cache {} arch.mthread_downloaded.error.cancelled=Operação cancelada arch.optdeps.checking=Verificando as dependências opcionais de {} +arch.package.requires_rebuild=Precisa ser reinstalado arch.providers=provedores arch.substatus.conflicts=Verificando conflitos arch.substatus.disk_space=Verificando espaço disponível em disco diff --git a/bauh/gems/arch/resources/locale/ru b/bauh/gems/arch/resources/locale/ru index a2434640..78098486 100644 --- a/bauh/gems/arch/resources/locale/ru +++ b/bauh/gems/arch/resources/locale/ru @@ -1,3 +1,9 @@ +arch.action.rebuild_check.allow=Allow reinstallation check +arch.action.rebuild_check.allow.status=Allowing reinstallation check +arch.action.rebuild_check.allow.confirm=Allow reinstallation check for {} ? +arch.action.rebuild_check.ignore=Ignore reinstallation check +arch.action.rebuild_check.ignore.status=Ignoring reinstallation check +arch.action.rebuild_check.ignore.confirm=Ignore reinstallation check for {} ? arch.action.db_locked.body.l1=The system package database is locked. arch.action.db_locked.body.l2=It is necessary to unlock it to continue. arch.action.db_locked.confirmation=Unlock and continue @@ -9,6 +15,10 @@ arch.action.disable_pkgbuild_edition.status=Unmarking PKGBUILD as editable arch.action.enable_pkgbuild_edition=Mark PKGBUILD as editable arch.action.enable_pkgbuild_edition.confirm=Mark PKGBUILD of {} as editable ? arch.action.enable_pkgbuild_edition.status=Marking PKGBUILD as editable +arch.action.reinstall=Reinstall +arch.action.reinstall.status=Reinstalling +arch.action.reinstall.confirm=Do you want to reinstall {} ? +arch.action.reinstall.error.no_apidata=It was not possible to retrieve information of {} from AUR arch.aur.action.edit_pkgbuild.body=Edit the PKGBUILD file of {} before continuing ? arch.aur.install.pgp.body=Для установки {} необходимо получить следующие PGP ключи arch.aur.install.pgp.receive_fail=Не удалось получить PGP-ключ {} @@ -31,8 +41,13 @@ arch.checking.conflicts=Проверка конфликтов с {} arch.checking.deps=Проверка зависимостей {} arch.checking.missing_deps=Проверка отсутствующих зависимостей {} arch.clone=Клонирование AUR-репозитория {} +arch.category.remove_from_aur=Removed from AUR arch.config.aur=пакеты AUR arch.config.aur.tip=Это позволяет управлять пакетами AUR. git должен быть установлен. +arch.config.aur_rebuild_detector=Check reinstallation need {} +arch.config.aur_rebuild_detector.tip=It checks if packages built with old library versions require to be rebuilt. If a package needs to be rebuilt, it will be marked for update ({} must be installed) +arch.config.aur_rebuild_detector_no_bin=Ignore binaries ({}) +arch.config.aur_rebuild_detector_no_bin.tip=If binary packages named as "package-bin" should be ignored by {} ({}) arch.config.automatch_providers=Auto-define dependency providers arch.config.automatch_providers.tip=It automatically chooses which provider will be used for a package dependency when both names are equal. arch.config.aur_build_dir=Build directory (AUR) @@ -200,6 +215,7 @@ arch.missing_deps_found=Отсутствуют зависимости для {} arch.mthread_downloaded.error.cache_dir=It was not possible to create the cache directory {} arch.mthread_downloaded.error.cancelled=Operation cancelled arch.optdeps.checking=Проверка необязательных обязательных зависимостей {} +arch.package.requires_rebuild=It needs to be reinstalled arch.providers=providers arch.substatus.conflicts=Checking for conflicts arch.substatus.disk_space=Checking available disk space diff --git a/bauh/gems/arch/resources/locale/tr b/bauh/gems/arch/resources/locale/tr index b770450d..ffe30afb 100644 --- a/bauh/gems/arch/resources/locale/tr +++ b/bauh/gems/arch/resources/locale/tr @@ -1,3 +1,9 @@ +arch.action.rebuild_check.allow=Allow reinstallation check +arch.action.rebuild_check.allow.status=Allowing reinstallation check +arch.action.rebuild_check.allow.confirm=Allow reinstallation check for {} ? +arch.action.rebuild_check.ignore=Ignore reinstallation check +arch.action.rebuild_check.ignore.status=Ignoring reinstallation check +arch.action.rebuild_check.ignore.confirm=Ignore reinstallation check for {} ? arch.action.db_locked.body.l1=Sistemin paket veritabanı kilitli. arch.action.db_locked.body.l2=Devam etmek için veritabanı kilidini açmak gerekiyor. arch.action.db_locked.confirmation=Veritabanı kilidini aç ve devam et @@ -9,6 +15,10 @@ arch.action.disable_pkgbuild_edition.status=Unmarking PKGBUILD as editable arch.action.enable_pkgbuild_edition=Mark PKGBUILD as editable arch.action.enable_pkgbuild_edition.confirm=Mark PKGBUILD of {} as editable ? arch.action.enable_pkgbuild_edition.status=Marking PKGBUILD as editable +arch.action.reinstall=Reinstall +arch.action.reinstall.status=Reinstalling +arch.action.reinstall.confirm=Do you want to reinstall {} ? +arch.action.reinstall.error.no_apidata=It was not possible to retrieve information of {} from AUR arch.aur.action.edit_pkgbuild.body=Edit the PKGBUILD file of {} before continuing ? arch.aur.install.pgp.body={} kurmak için aşağıdaki PGP anahtarlarını almak gereklidir arch.aur.install.pgp.receive_fail=PGP anahtarı alınamadı {} @@ -31,8 +41,13 @@ arch.checking.conflicts={} ile çakışmalar kontrol ediliyor arch.checking.deps={} bağımlılıkları kontrol ediliyor arch.checking.missing_deps={} eksik bağımlılıkları kontrol ediliyor arch.clone=AUR deposu kopyalanıyor {} +arch.category.remove_from_aur=Removed from AUR arch.config.aur=AUR paketleri arch.config.aur.tip=AUR paketlerinin yönetilmesine izin verir. git yüklenmeli. +arch.config.aur_rebuild_detector=Check reinstallation need {} +arch.config.aur_rebuild_detector.tip=It checks if packages built with old library versions require to be rebuilt. If a package needs to be rebuilt, it will be marked for update ({} must be installed) +arch.config.aur_rebuild_detector_no_bin=Ignore binaries ({}) +arch.config.aur_rebuild_detector_no_bin.tip=If binary packages named as "package-bin" should be ignored by {} ({}) arch.config.automatch_providers=Auto-define dependency providers arch.config.automatch_providers.tip=It automatically chooses which provider will be used for a package dependency when both names are equal. arch.config.aur_build_dir=Build directory (AUR) @@ -200,6 +215,7 @@ arch.missing_deps_found={} için eksik bağımlılıklar arch.mthread_downloaded.error.cache_dir=It was not possible to create the cache directory {} arch.mthread_downloaded.error.cancelled=Operation cancelled arch.optdeps.checking={} İsteğe bağlı bağımlılıkları kontrol et +arch.package.requires_rebuild=It needs to be reinstalled arch.providers=sağlayıcılar arch.substatus.conflicts=Checking for conflicts arch.substatus.disk_space=Checking available disk space diff --git a/bauh/gems/arch/worker.py b/bauh/gems/arch/worker.py index c055aa0b..e91e5c16 100644 --- a/bauh/gems/arch/worker.py +++ b/bauh/gems/arch/worker.py @@ -44,6 +44,9 @@ def __init__(self, context: ApplicationContext, taskman: TaskManager, create_con self.taskman.register_task(self.task_id, self.i18n['arch.task.aur.index.status'], get_icon_path()) def should_update(self) -> bool: + if not aur.is_supported(self.config): + return False + try: exp_hours = int(self.config['aur_idx_exp']) except: @@ -134,7 +137,7 @@ def run(self): tf = time.time() self.taskman.finish_task(self.task_id) - self.logger.info("Finished. Took {0:.2f} seconds".format(tf - ti)) + self.logger.info("Finished. Took {0:.5f} seconds".format(tf - ti)) class ArchDiskCacheUpdater(Thread): diff --git a/bauh/gems/flatpak/__init__.py b/bauh/gems/flatpak/__init__.py index e6a7328b..3bf219ab 100644 --- a/bauh/gems/flatpak/__init__.py +++ b/bauh/gems/flatpak/__init__.py @@ -1,6 +1,8 @@ import os from pathlib import Path +from pkg_resources import parse_version + from bauh.api.constants import CONFIG_PATH from bauh.commons import resource @@ -10,6 +12,10 @@ CONFIG_DIR = '{}/flatpak'.format(CONFIG_PATH) UPDATES_IGNORED_FILE = '{}/updates_ignored.txt'.format(CONFIG_DIR) EXPORTS_PATH = '{}/.local/share/flatpak/exports/share'.format(str(Path.home())) +VERSION_1_2 = parse_version('1.2') +VERSION_1_3 = parse_version('1.3') +VERSION_1_4 = parse_version('1.4') +VERSION_1_5 = parse_version('1.5') def get_icon_path() -> str: diff --git a/bauh/gems/flatpak/controller.py b/bauh/gems/flatpak/controller.py index 16465450..30e57a91 100644 --- a/bauh/gems/flatpak/controller.py +++ b/bauh/gems/flatpak/controller.py @@ -7,6 +7,8 @@ from threading import Thread from typing import List, Set, Type, Tuple, Optional +from packaging.version import Version + from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements, \ UpgradeRequirement, TransactionResult, SoftwareAction from bauh.api.abstract.disk import DiskCacheLoader @@ -20,7 +22,7 @@ from bauh.commons.html import strip_html, bold from bauh.commons.system import ProcessHandler from bauh.gems.flatpak import flatpak, SUGGESTIONS_FILE, CONFIG_FILE, UPDATES_IGNORED_FILE, CONFIG_DIR, EXPORTS_PATH, \ - get_icon_path + get_icon_path, VERSION_1_5, VERSION_1_4 from bauh.gems.flatpak.config import FlatpakConfigManager from bauh.gems.flatpak.constants import FLATHUB_API_URL from bauh.gems.flatpak.model import FlatpakApplication @@ -35,12 +37,12 @@ class FlatpakManager(SoftwareManager): def __init__(self, context: ApplicationContext): super(FlatpakManager, self).__init__(context=context) self.i18n = context.i18n - self.api_cache = context.cache_factory.new() - self.category_cache = context.cache_factory.new() + self.api_cache = context.cache_factory.new(None) + self.category_cache = context.cache_factory.new(None) context.disk_loader_factory.map(FlatpakApplication, self.api_cache) self.enabled = True self.http_client = context.http_client - self.suggestions_cache = context.cache_factory.new() + self.suggestions_cache = context.cache_factory.new(None) self.logger = context.logger self.configman = FlatpakConfigManager() @@ -111,7 +113,7 @@ def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_u res.total = len(res.installed) + len(res.new) return res - def _add_updates(self, version: str, output: list): + def _add_updates(self, version: Version, output: list): output.append(flatpak.list_updates_as_str(version)) 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: @@ -141,7 +143,7 @@ def read_installed(self, disk_loader: Optional[DiskCacheLoader], limit: int = -1 models.append(model) if update_map and (update_map['full'] or update_map['partial']): - if version >= '1.4.0': + if version >= VERSION_1_4: update_id = '{}/{}/{}'.format(app_json['id'], app_json['branch'], app_json['installation']) if update_map['full'] and update_id in update_map['full']: @@ -210,7 +212,7 @@ def upgrade(self, requirements: UpgradeRequirements, root_password: str, watcher related, deps = False, False ref = req.pkg.ref - if req.pkg.partial and flatpak_version < '1.5': + if req.pkg.partial and flatpak_version < VERSION_1_5: related, deps = True, True ref = req.pkg.base_ref @@ -252,10 +254,10 @@ def uninstall(self, pkg: FlatpakApplication, root_password: str, watcher: Proces def get_info(self, app: FlatpakApplication) -> dict: if app.installed: version = flatpak.get_version() - id_ = app.base_id if app.partial and version < '1.5' else app.id + id_ = app.base_id if app.partial and version < VERSION_1_5 else app.id app_info = flatpak.get_app_info_fields(id_, app.branch, app.installation) - if app.partial and version < '1.5': + if app.partial and version < VERSION_1_5: app_info['id'] = app.id app_info['ref'] = app.ref diff --git a/bauh/gems/flatpak/flatpak.py b/bauh/gems/flatpak/flatpak.py index f5e61286..201d7796 100755 --- a/bauh/gems/flatpak/flatpak.py +++ b/bauh/gems/flatpak/flatpak.py @@ -5,10 +5,13 @@ from datetime import datetime from typing import List, Dict, Set, Iterable, Optional +from packaging.version import Version +from pkg_resources import parse_version + from bauh.api.exception import NoInternetException from bauh.commons.system import new_subprocess, run_cmd, SimpleProcess, ProcessHandler from bauh.commons.util import size_to_byte -from bauh.gems.flatpak import EXPORTS_PATH +from bauh.gems.flatpak import EXPORTS_PATH, VERSION_1_3, VERSION_1_2, VERSION_1_5 RE_SEVERAL_SPACES = re.compile(r'\s+') RE_COMMIT = re.compile(r'(Latest commit|Commit)\s*:\s*(.+)') @@ -65,9 +68,9 @@ def is_installed(): return False if version is None else True -def get_version(): +def get_version() -> Optional[Version]: res = run_cmd('{} --version'.format('flatpak'), print_error=False) - return res.split(' ')[1].strip() if res else None + return parse_version(res.split(' ')[1].strip()) if res else None def get_app_info(app_id: str, branch: str, installation: str): @@ -87,11 +90,10 @@ def get_commit(app_id: str, branch: str, installation: str) -> Optional[str]: return commits[0][1].strip() -def list_installed(version: str) -> List[dict]: - +def list_installed(version: Version) -> List[dict]: apps = [] - if version < '1.2': + if version < VERSION_1_3: app_list = new_subprocess(['flatpak', 'list', '-d']) for o in app_list.stdout: @@ -114,7 +116,7 @@ def list_installed(version: str) -> List[dict]: }) else: - cols = 'application,ref,arch,branch,description,origin,options,{}version'.format('' if version < '1.3' else 'name,') + cols = 'application,ref,arch,branch,description,origin,options,{}version'.format('' if version < VERSION_1_3 else 'name,') app_list = new_subprocess(['flatpak', 'list', '--columns=' + cols]) for o in app_list.stdout: @@ -122,7 +124,7 @@ def list_installed(version: str) -> List[dict]: data = o.decode().strip().split('\t') runtime = 'runtime' in data[6] - if version < '1.3': + if version < VERSION_1_3: name = data[0].split('.')[-1] if len(data) > 7 and data[7]: @@ -172,7 +174,7 @@ def uninstall(app_ref: str, installation: str) -> SimpleProcess: shell=True) -def list_updates_as_str(version: str) -> Dict[str, set]: +def list_updates_as_str(version: Version) -> Dict[str, set]: updates = read_updates(version, 'system') user_updates = read_updates(version, 'user') @@ -182,9 +184,9 @@ def list_updates_as_str(version: str) -> Dict[str, set]: return updates -def read_updates(version: str, installation: str) -> Dict[str, set]: +def read_updates(version: Version, installation: str) -> Dict[str, set]: res = {'partial': set(), 'full': set()} - if version < '1.2': + if version < VERSION_1_2: try: output = run_cmd('{} update --no-related --no-deps --{}'.format('flatpak', installation), ignore_return_code=True) @@ -205,7 +207,7 @@ def read_updates(version: str, installation: str) -> Dict[str, set]: line_split = o.decode().strip().split('\t') if len(line_split) > 2: - if version >= '1.5.0': + if version >= VERSION_1_5: update_id = '{}/{}/{}'.format(line_split[2], line_split[3], installation) else: update_id = '{}/{}/{}'.format(line_split[2], line_split[4], installation) @@ -275,7 +277,7 @@ def get_app_commits_data(app_ref: str, origin: str, installation: str, full_str: return commits -def search(version: str, word: str, installation: str, app_id: bool = False) -> List[dict]: +def search(version: Version, word: str, installation: str, app_id: bool = False) -> List[dict]: res = run_cmd('{} search {} --{}'.format('flatpak', word, installation)) @@ -287,7 +289,7 @@ def search(version: str, word: str, installation: str, app_id: bool = False) -> for info in split_res: if info: info_list = info.split('\t') - if version >= '1.3.0': + if version >= VERSION_1_3: id_ = info_list[2].strip() if app_id and id_ != word: @@ -306,7 +308,7 @@ def search(version: str, word: str, installation: str, app_id: bool = False) -> 'arch': None, # unknown at this moment, 'ref': None # unknown at this moment } - elif version >= '1.2.0': + elif version >= VERSION_1_2: id_ = info_list[1].strip() if app_id and id_ != word: @@ -392,9 +394,9 @@ def run(app_id: str): subprocess.Popen(['flatpak run {}'.format(app_id)], shell=True, env={**os.environ}) -def map_update_download_size(app_ids: Iterable[str], installation: str, version: str) -> Dict[str, int]: +def map_update_download_size(app_ids: Iterable[str], installation: str, version: Version) -> Dict[str, int]: success, output = ProcessHandler().handle_simple(SimpleProcess(['flatpak', 'update', '--{}'.format(installation)])) - if version >= '1.5': + if version >= VERSION_1_5: res = {} p = re.compile(r'^\d+.\t') p2 = re.compile(r'\s([0-9.?a-zA-Z]+)\s?') @@ -411,12 +413,15 @@ def map_update_download_size(app_ids: Iterable[str], installation: str, version: related_id = [appid for appid in app_ids if appid == line_id] - if related_id: - size = p2.findall(line_split[6])[0].split('?') + if related_id and len(line_split) >= 7: + size_tuple = p2.findall(line_split[6]) + + if size_tuple: + size = size_tuple[0].split('?') - if size and len(size) > 1: - try: - res[related_id[0].strip()] = size_to_byte(float(size[0]), size[1]) - except: - traceback.print_exc() + if size and len(size) > 1: + try: + res[related_id[0].strip()] = size_to_byte(float(size[0]), size[1]) + except: + traceback.print_exc() return res diff --git a/bauh/view/core/controller.py b/bauh/view/core/controller.py index baca76d9..01b3c169 100755 --- a/bauh/view/core/controller.py +++ b/bauh/view/core/controller.py @@ -149,7 +149,7 @@ def _search(self, word: str, is_url: bool, man: SoftwareManager, disk_loader, re mti = time.time() apps_found = man.search(words=word, disk_loader=disk_loader, is_url=is_url) mtf = time.time() - self.logger.info(man.__class__.__name__ + " took {0:.2f} seconds".format(mtf - mti)) + self.logger.info(man.__class__.__name__ + " took {0:.8f} seconds".format(mtf - mti)) res.installed.extend(apps_found.installed) res.new.extend(apps_found.new) @@ -158,7 +158,7 @@ def search(self, words: str, disk_loader: DiskCacheLoader = None, limit: int = - ti = time.time() self._wait_to_be_ready() - res = SearchResult([], [], 0) + res = SearchResult.empty() if self.context.is_internet_available(): norm_word = words.strip().lower() @@ -183,12 +183,12 @@ def search(self, words: str, disk_loader: DiskCacheLoader = None, limit: int = - res.installed = self._sort(res.installed, norm_word) res.new = self._sort(res.new, norm_word) - res.total = len(res.installed) + len(res.new) else: raise NoInternetException() + res.update_total() tf = time.time() - self.logger.info('Took {0:.2f} seconds'.format(tf - ti)) + self.logger.info('Took {0:.8f} seconds'.format(tf - ti)) return res def _wait_to_be_ready(self): diff --git a/bauh/view/core/settings.py b/bauh/view/core/settings.py index 253da8d0..5bc659f0 100644 --- a/bauh/view/core/settings.py +++ b/bauh/view/core/settings.py @@ -1,8 +1,10 @@ import logging import os +import time import traceback from math import floor -from typing import List, Tuple, Optional +from threading import Thread +from typing import List, Tuple, Optional, Dict from PyQt5.QtWidgets import QApplication, QStyleFactory @@ -449,23 +451,50 @@ def _save_settings(self, general: PanelComponent, except: return False, [traceback.format_exc()] - def save_settings(self, component: TabGroupComponent) -> Tuple[bool, List[str]]: + def _save_manager_settings(self, man: SoftwareManager, panel: ViewComponent, success_map: Dict[str, bool], warnings: List[str]): + success = False + + try: + res = man.save_settings(panel) - saved, warnings = True, [] + if res: + success, errors = res[0], res[1] - bkp = component.get_tab('core.bkp') - success, errors = self._save_settings(general=component.get_tab('core.gen').content, - advanced=component.get_tab('core.adv').content, - tray=component.get_tab('core.tray').content, - backup=bkp.content if bkp else None, - ui=component.get_tab('core.ui').content, - gems_panel=component.get_tab('core.types').content) + if errors: + warnings.extend(errors) + except: + self.logger.error("An exception happened while {} was trying to save its settings".format(man.__class__.__name__)) + traceback.print_exc() + finally: + success_map[man.__class__.__name__] = success - if not success: - saved = False + def _save_core_settings(self, root_component: TabGroupComponent, success_map: Dict[str, bool], warnings: List[str]): + success = False - if errors: - warnings.extend(errors) + try: + bkp = root_component.get_tab('core.bkp') + success, errors = self._save_settings(general=root_component.get_tab('core.gen').content, + advanced=root_component.get_tab('core.adv').content, + tray=root_component.get_tab('core.tray').content, + backup=bkp.content if bkp else None, + ui=root_component.get_tab('core.ui').content, + gems_panel=root_component.get_tab('core.types').content) + if errors: + warnings.extend(errors) + + except: + self.logger.error("An exception happened while saving the core settings") + traceback.print_exc() + finally: + success_map[self.__class__.__name__] = success + + def save_settings(self, component: TabGroupComponent) -> Tuple[bool, List[str]]: + ti = time.time() + save_threads, warnings, success_map = [], [], {} + + save_core = Thread(target=self._save_core_settings, args=(component, success_map, warnings), daemon=True) + save_core.start() + save_threads.append(save_core) for man in self.managers: if man: @@ -475,18 +504,18 @@ def save_settings(self, component: TabGroupComponent) -> Tuple[bool, List[str]]: if not tab: self.logger.warning("Tab for {} was not found".format(man.__class__.__name__)) else: - res = man.save_settings(tab.content) - - if res: - success, errors = res[0], res[1] + save_man = Thread(target=self._save_manager_settings(man, tab.content, success_map, warnings), daemon=True) + save_man.start() + save_threads.append(save_man) - if not success: - saved = False + for t in save_threads: + t.join() - if errors: - warnings.extend(errors) + success = all(success_map.values()) - return saved, warnings + tf = time.time() + self.logger.info("Saving all settings took {0:.8f} seconds".format(tf - ti)) + return success, warnings def _gen_backup_settings(self, core_config: dict, screen_width: int, screen_height: int) -> TabComponent: if timeshift.is_available(): diff --git a/bauh/view/qt/apps_table.py b/bauh/view/qt/apps_table.py index 44463bb1..cf894fe5 100644 --- a/bauh/view/qt/apps_table.py +++ b/bauh/view/qt/apps_table.py @@ -15,7 +15,6 @@ from bauh.commons.html import strip_html, bold from bauh.view.qt.components import IconButton, QCustomMenuAction, QCustomToolbar from bauh.view.qt.dialog import ConfirmationDialog -from bauh.view.qt.qt_utils import measure_based_on_height from bauh.view.qt.view_model import PackageView from bauh.view.util.translation import I18n @@ -71,6 +70,7 @@ def change_state(self, not_checked: bool): class PackagesTable(QTableWidget): COL_NUMBER = 9 + DEFAULT_ICON_SIZE = QSize(16, 16) def __init__(self, parent: QWidget, icon_cache: MemoryCache, download_icons: bool): super(PackagesTable, self).__init__() @@ -100,10 +100,6 @@ def __init__(self, parent: QWidget, icon_cache: MemoryCache, download_icons: boo self.cache_type_icon = {} self.i18n = self.window.i18n - def icon_size(self) -> QSize: - pixels = measure_based_on_height(0.02083) - return QSize(pixels, pixels) - def has_any_settings(self, pkg: PackageView): return pkg.model.has_history() or \ pkg.model.can_be_downgraded() or \ @@ -348,13 +344,13 @@ def _set_col_type(self, col: int, pkg: PackageView): icon_data = self.cache_type_icon.get(pkg.model.get_type()) if icon_data is None: - pixmap = QIcon(pkg.model.get_type_icon_path()).pixmap(self.icon_size()) + icon = QIcon(pkg.model.get_type_icon_path()) + pixmap = icon.pixmap(self._get_icon_size(icon)) icon_data = {'px': pixmap, 'tip': '{}: {}'.format(self.i18n['type'], pkg.get_type_label())} self.cache_type_icon[pkg.model.get_type()] = icon_data col_type_icon = QLabel() col_type_icon.setCursor(QCursor(Qt.WhatsThisCursor)) - col_type_icon.setObjectName('app_type') col_type_icon.setProperty('icon', 'true') col_type_icon.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) col_type_icon.setPixmap(icon_data['px']) @@ -379,7 +375,7 @@ def _set_col_version(self, col: int, pkg: PackageView): if pkg.model.update and not pkg.model.is_update_ignored(): label_version.setProperty('update', 'true') - tooltip = self.i18n['version.installed_outdated'] + tooltip = pkg.model.get_update_tip() or self.i18n['version.installed_outdated'] if pkg.model.is_update_ignored(): label_version.setProperty('ignored', 'true') @@ -424,7 +420,6 @@ def _set_col_icon(self, col: int, pkg: PackageView): icon = icon_data['icon'] if icon_data else QIcon(pkg.model.get_default_icon_path()) col_icon = QLabel() - col_icon.setObjectName('app_icon') col_icon.setProperty('icon', 'true') col_icon.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) self._update_icon(col_icon, icon) @@ -453,7 +448,11 @@ def _set_col_name(self, col: int, pkg: PackageView): self.setCellWidget(pkg.table_index, col, col_name) def _update_icon(self, label: QLabel, icon: QIcon): - label.setPixmap(icon.pixmap(QSize(self.icon_size()))) + label.setPixmap(icon.pixmap(self._get_icon_size(icon))) + + def _get_icon_size(self, icon: QIcon) -> QSize: + sizes = icon.availableSizes() + return sizes[-1] if sizes else self.DEFAULT_ICON_SIZE def _set_col_description(self, col: int, pkg: PackageView): item = QLabel() diff --git a/bauh/view/qt/components.py b/bauh/view/qt/components.py index 39823180..e389207e 100644 --- a/bauh/view/qt/components.py +++ b/bauh/view/qt/components.py @@ -766,6 +766,7 @@ def gen_tip_icon(self, tip: str) -> QLabel: tip_icon = QLabel() tip_icon.setProperty('tip_icon', 'true') tip_icon.setToolTip(tip.strip()) + tip_icon.setCursor(QCursor(Qt.WhatsThisCursor)) return tip_icon def _new_text_input(self, c: TextInputComponent) -> Tuple[QLabel, QLineEdit]: diff --git a/bauh/view/qt/prepare.py b/bauh/view/qt/prepare.py index 65a7f930..9998afaa 100644 --- a/bauh/view/qt/prepare.py +++ b/bauh/view/qt/prepare.py @@ -2,10 +2,9 @@ import operator import time from functools import reduce -from threading import Lock from typing import Tuple, Optional -from PyQt5.QtCore import QSize, Qt, QThread, pyqtSignal, QCoreApplication +from PyQt5.QtCore import QSize, Qt, QThread, pyqtSignal, QCoreApplication, QMutex from PyQt5.QtGui import QIcon, QCursor, QCloseEvent from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy, QTableWidget, QHeaderView, QPushButton, \ QProgressBar, QPlainTextEdit, QToolButton, QHBoxLayout @@ -38,8 +37,8 @@ def __init__(self, context: ApplicationContext, manager: SoftwareManager, i18n: self.password_response = None self._tasks_added = set() self._tasks_finished = set() - self._add_lock = Lock() - self._finish_lock = Lock() + self._add_lock = QMutex() + self._finish_lock = QMutex() def ask_password(self) -> Tuple[bool, Optional[str]]: self.waiting_password = True @@ -63,42 +62,44 @@ def run(self): QCoreApplication.exit(1) self.manager.prepare(self, root_pwd, None) - self._add_lock.acquire() self.signal_started.emit(len(self._tasks_added)) - self._add_lock.release() def update_progress(self, task_id: str, progress: float, substatus: str): + self._add_lock.lock() if task_id in self._tasks_added: self.signal_update.emit(task_id, progress, substatus) + self._add_lock.unlock() def update_output(self, task_id: str, output: str): + self._add_lock.lock() if task_id in self._tasks_added: self.signal_output.emit(task_id, output) + self._add_lock.unlock() def register_task(self, id_: str, label: str, icon_path: str): - self._add_lock.acquire() + self._add_lock.lock() if id_ not in self._tasks_added: self._tasks_added.add(id_) self.signal_register.emit(id_, label, icon_path) - self._add_lock.release() + self._add_lock.unlock() def finish_task(self, task_id: str): - self._add_lock.acquire() + self._add_lock.lock() task_registered = task_id in self._tasks_added - self._add_lock.release() + self._add_lock.unlock() if not task_registered: return - self._finish_lock.acquire() + self._finish_lock.lock() if task_id not in self._tasks_finished: self._tasks_finished.add(task_id) self.signal_finished.emit(task_id) - self._finish_lock.release() + self._finish_lock.unlock() class CheckFinished(QThread): diff --git a/bauh/view/qt/settings.py b/bauh/view/qt/settings.py index fdf1f9c7..11b98737 100644 --- a/bauh/view/qt/settings.py +++ b/bauh/view/qt/settings.py @@ -2,9 +2,9 @@ from io import StringIO from typing import Optional -from PyQt5.QtCore import QSize, Qt, QCoreApplication +from PyQt5.QtCore import QSize, Qt, QCoreApplication, QThread, pyqtSignal from PyQt5.QtGui import QCursor -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QSizePolicy, QPushButton, QHBoxLayout +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QSizePolicy, QPushButton, QHBoxLayout, QApplication from bauh import __app_name__ from bauh.api.abstract.controller import SoftwareManager @@ -18,6 +18,22 @@ from bauh.view.util.translation import I18n +class ReloadManagePanel(QThread): + + signal_finished = pyqtSignal() + + def __init__(self, manager: SoftwareManager): + super(ReloadManagePanel, self).__init__() + self.manager = manager + + def run(self): + if isinstance(self.manager, GenericSoftwareManager): + self.manager.reset_cache() + + self.manager.prepare(task_manager=None, root_password=None, internet_available=None) + self.signal_finished.emit() + + class SettingsWindow(QWidget): def __init__(self, manager: SoftwareManager, i18n: I18n, screen_size: QSize, window: QWidget, parent: Optional[QWidget] = None): @@ -31,9 +47,9 @@ def __init__(self, manager: SoftwareManager, i18n: I18n, screen_size: QSize, win self.settings_model = self.manager.get_settings(screen_size.width(), screen_size.height()) - tab_group = to_widget(self.settings_model, i18n) - tab_group.setObjectName('settings') - self.layout().addWidget(tab_group) + self.tab_group = to_widget(self.settings_model, i18n) + self.tab_group.setObjectName('settings') + self.layout().addWidget(self.tab_group) lower_container = QWidget() lower_container.setObjectName('lower_container') @@ -41,26 +57,29 @@ def __init__(self, manager: SoftwareManager, i18n: I18n, screen_size: QSize, win lower_container.setLayout(QHBoxLayout()) lower_container.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) - bt_close = QPushButton() - bt_close.setObjectName('cancel') - bt_close.setAutoDefault(True) - bt_close.setCursor(QCursor(Qt.PointingHandCursor)) - bt_close.setText(self.i18n['close'].capitalize()) - bt_close.clicked.connect(lambda: self.close()) - lower_container.layout().addWidget(bt_close) + self.bt_close = QPushButton() + self.bt_close.setObjectName('cancel') + self.bt_close.setAutoDefault(True) + self.bt_close.setCursor(QCursor(Qt.PointingHandCursor)) + self.bt_close.setText(self.i18n['close'].capitalize()) + self.bt_close.clicked.connect(lambda: self.close()) + lower_container.layout().addWidget(self.bt_close) lower_container.layout().addWidget(new_spacer()) - bt_change = QPushButton() - bt_change.setAutoDefault(True) - bt_change.setObjectName('ok') - bt_change.setCursor(QCursor(Qt.PointingHandCursor)) - bt_change.setText(self.i18n['change'].capitalize()) - bt_change.clicked.connect(self._save_settings) - lower_container.layout().addWidget(bt_change) + self.bt_change = QPushButton() + self.bt_change.setAutoDefault(True) + self.bt_change.setObjectName('ok') + self.bt_change.setCursor(QCursor(Qt.PointingHandCursor)) + self.bt_change.setText(self.i18n['change'].capitalize()) + self.bt_change.clicked.connect(self._save_settings) + lower_container.layout().addWidget(self.bt_change) self.layout().addWidget(lower_container) + self.thread_reload_panel = ReloadManagePanel(manager=manager) + self.thread_reload_panel.signal_finished.connect(self._reload_manage_panel) + def show(self): super(SettingsWindow, self).show() centralize(self) @@ -83,6 +102,10 @@ def handle_display(self): self.setWindowState(self.windowState() and Qt.WindowMinimized or Qt.WindowActive) def _save_settings(self): + self.tab_group.setEnabled(False) + self.bt_change.setEnabled(False) + self.bt_close.setEnabled(False) + success, warnings = self.manager.save_settings(self.settings_model) if success: @@ -98,17 +121,8 @@ def _save_settings(self): self.close() util.restart_app() else: - if isinstance(self.manager, GenericSoftwareManager): - self.manager.reset_cache() - - self.manager.prepare(task_manager=None, root_password=None, internet_available=None) - - if self.window and self.window.isVisible(): - self.window.update_custom_actions() - self.window.verify_warnings() - self.window.types_changed = True - self.window.begin_refresh_packages() - self.close() + self.thread_reload_panel.start() + QApplication.setOverrideCursor(Qt.WaitCursor) else: msg = StringIO() msg.write("{}
".format(self.i18n['settings.error'])) @@ -118,3 +132,10 @@ def _save_settings(self): msg.seek(0) dialog.show_message(title=self.i18n['warning'].capitalize(), body=msg.read(), type_=MessageType.WARNING) + + def _reload_manage_panel(self): + if self.window and self.window.isVisible(): + self.window.reload() + + QApplication.restoreOverrideCursor() + self.close() diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py index e386138b..b0046a57 100644 --- a/bauh/view/qt/thread.py +++ b/bauh/view/qt/thread.py @@ -8,7 +8,7 @@ from typing import List, Type, Set, Tuple, Optional import requests -from PyQt5.QtCore import QThread, pyqtSignal +from PyQt5.QtCore import QThread, pyqtSignal, QObject from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QWidget @@ -644,7 +644,12 @@ def __init__(self, manager: SoftwareManager, pkg: PackageView = None): def run(self): if self.pkg: info = {'__app__': self.pkg} - info.update(self.manager.get_info(self.pkg.model)) + + pkg_info = self.manager.get_info(self.pkg.model) + + if pkg_info: + info.update(pkg_info) + self.notify_finished(info) self.pkg = None @@ -1089,3 +1094,18 @@ def run(self): configman.save_config(core_config) except: traceback.print_exc() + + +class StartAsyncAction(QThread): + + signal_start = pyqtSignal() + + def __init__(self, delay_in_milis: int = -1, parent: Optional[QObject] = None): + super(StartAsyncAction, self).__init__(parent=parent) + self.delay = delay_in_milis + + def run(self): + if self.delay > 0: + self.msleep(self.delay) + + self.signal_start.emit() diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py index d40d5a89..9c3faca2 100755 --- a/bauh/view/qt/window.py +++ b/bauh/view/qt/window.py @@ -41,7 +41,7 @@ ListWarnings, \ AsyncAction, LaunchPackage, ApplyFilters, CustomSoftwareAction, ShowScreenshots, CustomAction, \ NotifyInstalledLoaded, \ - IgnorePackageUpdates, SaveTheme + IgnorePackageUpdates, SaveTheme, StartAsyncAction from bauh.view.qt.view_model import PackageView, PackageViewStatus from bauh.view.util import util, resource from bauh.view.util.translation import I18n @@ -347,6 +347,9 @@ def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager self.thread_ignore_updates = IgnorePackageUpdates(manager=self.manager) self._bind_async_action(self.thread_ignore_updates, finished_call=self.finish_ignore_updates) + self.thread_reload = StartAsyncAction(delay_in_milis=5) + self.thread_reload.signal_start.connect(self._reload) + self.container_bottom = QWidget() self.container_bottom.setObjectName('container_bottom') self.container_bottom.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) @@ -837,7 +840,11 @@ def _update_table(self, pkgs_info: dict, signal: bool = False): self.table_apps.change_headers_policy(QHeaderView.Stretch) self.table_apps.change_headers_policy() self._resize(accept_lower_width=len(self.pkgs) > 0) - self.label_displayed.setText('{} / {}'.format(len(self.pkgs), len(self.pkgs_available))) + + if len(self.pkgs) == 0 and len(self.pkgs_available) == 0: + self.label_displayed.setText('') + else: + self.label_displayed.setText('{} / {}'.format(len(self.pkgs), len(self.pkgs_available))) else: self.label_displayed.hide() @@ -1216,9 +1223,16 @@ def begin_show_info(self, pkg: dict): def _finish_show_info(self, pkg_info: dict): self._finish_action(action_id=ACTION_INFO) - dialog_info = InfoDialog(pkg_info=pkg_info, icon_cache=self.icon_cache, - i18n=self.i18n, screen_size=self.screen_size) - dialog_info.exec_() + + if pkg_info: + if len(pkg_info) > 1: + dialog_info = InfoDialog(pkg_info=pkg_info, icon_cache=self.icon_cache, + i18n=self.i18n, screen_size=self.screen_size) + dialog_info.exec_() + else: + dialog.show_message(title=self.i18n['warning'].capitalize(), + body=self.i18n['manage_window.info.no_info'].format(bold(pkg_info['__app__'].model.name)), + type_=MessageType.WARNING) def begin_show_screenshots(self, pkg: PackageView): self._begin_action(action_label=self.i18n['manage_window.status.screenshots'].format(bold(pkg.model.name)), @@ -1610,3 +1624,12 @@ def _map_theme_actions(self, menu: QMenu) -> List[QCustomMenuAction]: actions.sort(key=lambda a: a.get_label()) actions.insert(0, current_action) return actions + + def reload(self): + self.thread_reload.start() + + def _reload(self): + self.update_custom_actions() + self.verify_warnings() + self.types_changed = True + self.begin_refresh_packages() diff --git a/bauh/view/resources/locale/ca b/bauh/view/resources/locale/ca index 95c5812c..b81a15bf 100644 --- a/bauh/view/resources/locale/ca +++ b/bauh/view/resources/locale/ca @@ -316,6 +316,7 @@ manage_window.checkbox.show_details=Mostra detalls manage_window.columns.installed=Installed manage_window.columns.latest_version=Versió més recent manage_window.columns.update=Voleu actualitzar? +manage_window.info.no_info=There is no information available for {} manage_window.label.apps_displayed.tip=Applications displayed / available manage_window.label.updates=Actualitzacions manage_window.name_filter.button_tooltip=Click here to filter apps by name diff --git a/bauh/view/resources/locale/de b/bauh/view/resources/locale/de index 8b555c43..7054b55c 100644 --- a/bauh/view/resources/locale/de +++ b/bauh/view/resources/locale/de @@ -315,6 +315,7 @@ manage_window.checkbox.show_details=Details anzeigen manage_window.columns.installed=Installed manage_window.columns.latest_version=Neueste Version manage_window.columns.update=Upgraden? +manage_window.info.no_info=There is no information available for {} manage_window.label.apps_displayed.tip=Applications displayed / available manage_window.label.updates=Updates manage_window.name_filter.button_tooltip=Click here to filter apps by name diff --git a/bauh/view/resources/locale/en b/bauh/view/resources/locale/en index acd65ae9..8bc8ab8b 100644 --- a/bauh/view/resources/locale/en +++ b/bauh/view/resources/locale/en @@ -316,6 +316,7 @@ manage_window.checkbox.show_details=Show details manage_window.columns.installed=Installed manage_window.columns.latest_version=Latest Version manage_window.columns.update=Upgrade ? +manage_window.info.no_info=There is no information available for {} manage_window.label.apps_displayed.tip=Applications displayed / available manage_window.label.updates=Updates manage_window.name_filter.button_tooltip=Click here to filter apps by name diff --git a/bauh/view/resources/locale/es b/bauh/view/resources/locale/es index 72d12dd0..99eb8c7d 100644 --- a/bauh/view/resources/locale/es +++ b/bauh/view/resources/locale/es @@ -317,6 +317,7 @@ manage_window.checkbox.show_details=Mostrar detalles manage_window.columns.installed=Instaladas manage_window.columns.latest_version=Versión más reciente manage_window.columns.update=¿Quiere actualizar? +manage_window.info.no_info=No hay información disponible para {} manage_window.label.apps_displayed.tip=Applicaciones mostradas / disponibles manage_window.label.updates=Actualizaciones manage_window.name_filter.button_tooltip=Haga clic aquí para filtrar aplicaciones por nombre diff --git a/bauh/view/resources/locale/fr b/bauh/view/resources/locale/fr index 30e3bd23..f4af78a4 100644 --- a/bauh/view/resources/locale/fr +++ b/bauh/view/resources/locale/fr @@ -314,6 +314,7 @@ manage_window.checkbox.show_details=Détails manage_window.columns.installed=Installé manage_window.columns.latest_version=Dernière Version manage_window.columns.update=Mettre à jour ? +manage_window.info.no_info=There is no information available for {} manage_window.label.updates=Mises à jour manage_window.name_filter.placeholder=Filtrer par nom manage_window.name_filter.tooltip=Taper ici pour filtrer les applications par nom diff --git a/bauh/view/resources/locale/it b/bauh/view/resources/locale/it index 2e9e5d6c..c72407e6 100644 --- a/bauh/view/resources/locale/it +++ b/bauh/view/resources/locale/it @@ -317,6 +317,7 @@ manage_window.checkbox.show_details=Mostra dettagli manage_window.columns.installed=Installed manage_window.columns.latest_version=Ultima Versione manage_window.columns.update=Upgrade ? +manage_window.info.no_info=There is no information available for {} manage_window.label.apps_displayed.tip=Applications displayed / available manage_window.label.updates=Aggiornamenti manage_window.name_filter.button_tooltip=Click here to filter apps by name diff --git a/bauh/view/resources/locale/pt b/bauh/view/resources/locale/pt index 58e7b48f..2166889e 100644 --- a/bauh/view/resources/locale/pt +++ b/bauh/view/resources/locale/pt @@ -316,6 +316,7 @@ manage_window.checkbox.show_details=Mostrar detalhes manage_window.columns.installed=Instalado manage_window.columns.latest_version=Última Versão manage_window.columns.update=Atualizar ? +manage_window.info.no_info=Não há informação disponível para {} manage_window.label.apps_displayed.tip=Aplicações exibidas / disponíveis manage_window.label.updates=Atualizações manage_window.name_filter.button_tooltip=Clique aqui para filtrar aplicativos por nome diff --git a/bauh/view/resources/locale/ru b/bauh/view/resources/locale/ru index 3777c7ac..09fe56f9 100644 --- a/bauh/view/resources/locale/ru +++ b/bauh/view/resources/locale/ru @@ -315,6 +315,7 @@ manage_window.checkbox.show_details=Показать детали manage_window.columns.installed=Installed manage_window.columns.latest_version=Последняя версия manage_window.columns.update=Обновить? +manage_window.info.no_info=There is no information available for {} manage_window.label.apps_displayed.tip=Applications displayed / available manage_window.label.updates=Обновления manage_window.name_filter.button_tooltip=Click here to filter apps by name diff --git a/bauh/view/resources/locale/tr b/bauh/view/resources/locale/tr index 84db32dc..83e08628 100644 --- a/bauh/view/resources/locale/tr +++ b/bauh/view/resources/locale/tr @@ -315,6 +315,7 @@ manage_window.checkbox.show_details=Detayları göster manage_window.columns.installed=Yüklü manage_window.columns.latest_version=Son sürüm manage_window.columns.update=Yükselt ? +manage_window.info.no_info=There is no information available for {} manage_window.label.apps_displayed.tip=Applications displayed / available manage_window.label.updates=Güncellemeler manage_window.name_filter.button_tooltip=Click here to filter apps by name diff --git a/bauh/view/resources/style/default/default.qss b/bauh/view/resources/style/default/default.qss index e00e2cfd..1d3f55b9 100644 --- a/bauh/view/resources/style/default/default.qss +++ b/bauh/view/resources/style/default/default.qss @@ -268,7 +268,14 @@ PackagesTable QLabel#icon_publisher_verified { } PackagesTable QLabel[icon = "true"] { - qproperty-alignment: AlignCenter; + qproperty-scaledContents: True; + min-height: 16; + max-height: 16; + min-width: 16; + max-width: 16; + margin-left: 12px; + margin-right: 12px; + margin-top: 7px; } PackagesTable QToolButton#bt_install { diff --git a/bauh/view/util/disk.py b/bauh/view/util/disk.py index 058b21cf..786ed4e0 100644 --- a/bauh/view/util/disk.py +++ b/bauh/view/util/disk.py @@ -22,6 +22,7 @@ def __init__(self, cache_map: Dict[Type[SoftwarePackage], MemoryCache], logger: self.cache_map = cache_map self.logger = logger self.processed = 0 + self._working = False def fill(self, pkg: SoftwarePackage, sync: bool = False): """ @@ -31,7 +32,7 @@ def fill(self, pkg: SoftwarePackage, sync: bool = False): :return: """ if pkg and pkg.supports_disk_cache(): - if sync: + if sync or not self._working: self._fill_cached_data(pkg) else: self.pkgs.append(pkg) @@ -40,6 +41,7 @@ def stop_working(self): self._work = False def run(self): + self._working = True last = 0 while True: @@ -53,6 +55,8 @@ def run(self): elif not self._work: break + self._working = False + def _fill_cached_data(self, pkg: SoftwarePackage) -> bool: if os.path.exists(pkg.get_disk_data_path()): disk_path = pkg.get_disk_data_path()