From 9c9836bebf4a7a055d9db90860685d9ce91a5bf7 Mon Sep 17 00:00:00 2001 From: Vinicius Moreira Date: Thu, 7 Apr 2022 17:27:16 -0300 Subject: [PATCH] [arch] improvement: missing dependencies dialog now displays the packages sizes and descriptions (only from repositories) --- CHANGELOG.md | 1 + bauh/gems/arch/confirmation.py | 62 ++++++++++--- bauh/gems/arch/controller.py | 6 +- bauh/gems/arch/pacman.py | 155 +++++++++++++++++---------------- 4 files changed, 134 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f99e300..21a4124c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Improvements - Arch - install: + - missing dependencies dialog now displays the packages sizes and descriptions (only from repositories) - optional packages installation dialog appearance (aligned with other dependencies dialogs) - uninstall: - displaying hard and unnecessary requirements versions and descriptions diff --git a/bauh/gems/arch/confirmation.py b/bauh/gems/arch/confirmation.py index 67d2278c..3632de09 100644 --- a/bauh/gems/arch/confirmation.py +++ b/bauh/gems/arch/confirmation.py @@ -1,3 +1,4 @@ +from io import StringIO from typing import Set, Tuple, Dict, Collection from bauh.api.abstract.handler import ProcessWatcher @@ -47,27 +48,64 @@ def request_optional_deps(pkgname: str, pkg_repos: dict, watcher: ProcessWatcher return {o.value for o in view_opts.values} -def request_install_missing_deps(deps: Collection[Tuple[str, str]], watcher: ProcessWatcher, i18n: I18n) -> bool: - msg = f"

{i18n['arch.missing_deps.body'].format(deps=bold(str(len(deps))))}:

" - +def confirm_missing_deps(deps: Collection[Tuple[str, str]], watcher: ProcessWatcher, i18n: I18n) -> bool: opts = [] - repo_deps = [d[0] for d in deps if d[1] != 'aur'] - sizes = pacman.map_update_sizes(repo_deps) if repo_deps else {} + total_isize, total_dsize = None, None + pkgs_data = pacman.map_updates_data(pkgs=tuple(d[0] for d in deps if d[1] != 'aur'), description=True) or dict() for dep in deps: - size = sizes.get(dep[0]) - op = InputOption('{} ({}: {}) - {}: {}'.format(dep[0], - i18n['repository'], - dep[1].lower(), - i18n['size'].capitalize(), - get_human_size_str(size) if size is not None else '?'), dep[0]) + ver, desc, isize, dsize = None, None, None, None + data = pkgs_data.get(dep[0]) + + if data: + desc, isize, dsize = (data.get(f) for f in ('des', 's', 'ds')) + + if isize is not None: + if total_isize is None: + total_isize = 0 + + total_isize += isize + + if dsize is not None: + if total_dsize is None: + total_dsize = 0 + + total_dsize += dsize + + label = f"{dep[0]} ({i18n['repository']}: {dep[1].lower()}) | " \ + f"{i18n['size'].capitalize()}: {get_human_size_str(isize) if isize is not None else '?'}" \ + f"{' ({}: {})'.format(i18n['download'].capitalize(), get_human_size_str(dsize)) if dsize else ''}" + + op = InputOption(label=label, value=dep[0], tooltip=desc) op.read_only = True op.icon_path = _get_repo_icon(dep[1]) opts.append(op) comp = MultipleSelectComponent(label='', options=opts, default_options=set(opts)) - return watcher.request_confirmation(i18n['arch.missing_deps.title'], msg, [comp], confirmation_label=i18n['continue'].capitalize(), deny_label=i18n['cancel'].capitalize(), + + body = StringIO() + body.write('

') + body.write(i18n['arch.missing_deps.body'].format(deps=bold(str(len(deps))))) + + if total_isize is not None or total_dsize is not None: + body.write(' (') + + if total_isize is not None: + body.write(f"{i18n['size'].capitalize()}: {bold(get_human_size_str(total_isize))} | ") + + if total_dsize is not None: + body.write(f"{i18n['download'].capitalize()}: {bold(get_human_size_str(total_dsize))}") + + body.write(')') + + body.write(':

') + + return watcher.request_confirmation(title=i18n['arch.missing_deps.title'], + body=body.getvalue(), + components=[comp], + confirmation_label=i18n['continue'].capitalize(), + deny_label=i18n['cancel'].capitalize(), min_width=600) diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index a4c09617..fbf511cb 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -41,6 +41,7 @@ from bauh.gems.arch import aur, pacman, message, confirmation, disk, git, \ ARCH_CONFIG_DIR, EDITABLE_PKGBUILDS_FILE, URL_GPG_SERVERS, rebuild_detector, makepkg, sshell, get_repo_icon_path from bauh.gems.arch.aur import AURClient from bauh.gems.arch.config import get_build_dir, ArchConfigManager +from bauh.gems.arch.confirmation import confirm_missing_deps from bauh.gems.arch.dependencies import DependenciesAnalyser from bauh.gems.arch.download import MultithreadedDownloadService, ArchDownloadException from bauh.gems.arch.exceptions import PackageNotFoundException, PackageInHoldException @@ -2114,7 +2115,7 @@ class ArchManager(SoftwareManager): def _ask_and_install_missing_deps(self, context: TransactionContext, missing_deps: List[Tuple[str, str]]) -> bool: context.watcher.change_substatus(self.i18n['arch.missing_deps_found'].format(bold(context.name))) - if not confirmation.request_install_missing_deps(missing_deps, context.watcher, self.i18n): + if not confirm_missing_deps(missing_deps, context.watcher, self.i18n): context.watcher.print(self.i18n['action.cancelled']) return False @@ -2304,8 +2305,7 @@ class ArchManager(SoftwareManager): sorted_deps = sorting.sort(to_sort, {**deps_data, **subdeps_data}, provided_map) - if display_deps_dialog and not confirmation.request_install_missing_deps(sorted_deps, context.watcher, - self.i18n): + if display_deps_dialog and not confirm_missing_deps(sorted_deps, context.watcher, self.i18n): context.watcher.print(self.i18n['action.cancelled']) return True # because the main package installation was successful diff --git a/bauh/gems/arch/pacman.py b/bauh/gems/arch/pacman.py index be4c1c7e..4a4ac20b 100644 --- a/bauh/gems/arch/pacman.py +++ b/bauh/gems/arch/pacman.py @@ -599,40 +599,87 @@ def list_download_data(pkgs: Iterable[str]) -> List[Dict[str, str]]: return res -def map_updates_data(pkgs: Iterable[str], files: bool = False) -> dict: - if files: - output = run_cmd('pacman -Qi -p {}'.format(' '.join(pkgs))) - else: - output = run_cmd('pacman -Si {}'.format(' '.join(pkgs))) +def map_updates_data(pkgs: Iterable[str], files: bool = False, description: bool = False) -> Optional[Dict[str, Dict[str, object]]]: + if pkgs: + if files: + output = run_cmd('pacman -Qi -p {}'.format(' '.join(pkgs))) + else: + output = run_cmd('pacman -Si {}'.format(' '.join(pkgs))) - res = {} - if output: - latest_name = None - data = {'ds': None, 's': None, 'v': None, 'c': None, 'p': None, 'd': None, 'r': None} - latest_field = None + res = {} + if output: + latest_name = None + data = {'ds': None, 's': None, 'v': None, 'c': None, 'p': None, 'd': None, 'r': None, 'des': None} + latest_field = None - for l in output.split('\n'): - if l: - if l[0] != ' ': - line = l.strip() - field_sep_idx = line.index(':') - field = line[0:field_sep_idx].strip() - val = line[field_sep_idx + 1:].strip() + for l in output.split('\n'): + if l: + if l[0] != ' ': + line = l.strip() + field_sep_idx = line.index(':') + field = line[0:field_sep_idx].strip() + val = line[field_sep_idx + 1:].strip() - if field == 'Repository': - data['r'] = val - latest_field = 'r' - elif field == 'Name': - latest_name = val - latest_field = 'n' - elif field == 'Version': - data['v'] = val.split('=')[0] - latest_field = 'v' - elif field == 'Provides': - latest_field = 'p' - data['p'] = {latest_name, '{}={}'.format(latest_name, data['v'])} - if val != 'None': - for w in val.split(' '): + if field == 'Repository': + data['r'] = val + latest_field = 'r' + elif field == 'Name': + latest_name = val + latest_field = 'n' + elif field == 'Version': + data['v'] = val.split('=')[0] + latest_field = 'v' + elif description and field == 'Description': + data['des'] = val + latest_field = 'des' + elif field == 'Provides': + latest_field = 'p' + data['p'] = {latest_name, '{}={}'.format(latest_name, data['v'])} + if val != 'None': + for w in val.split(' '): + if w: + word = w.strip() + data['p'].add(word) + + word_split = word.split('=') + + if word_split[0] != word: + data['p'].add(word_split[0]) + elif field == 'Depends On': + val = val.strip() + + if val == 'None': + data['d'] = None + else: + data['d'] = {w.strip() for w in val.split(' ') if w} + latest_field = 'd' + elif field == 'Conflicts With': + if val == 'None': + data['c'] = None + else: + data['c'] = {w.strip() for w in val.split(' ') if w} + + latest_field = 'c' + elif field == 'Download Size': + size = val.split(' ') + data['ds'] = size_to_byte(size[0], size[1]) + latest_field = 'ds' + elif field == 'Installed Size': + size = val.split(' ') + data['s'] = size_to_byte(size[0], size[1]) + latest_field = 's' + elif latest_name and latest_field == 's': + res[latest_name] = data + latest_name = None + latest_field = None + data = {'ds': None, 's': None, 'c': None, 'p': None, 'd': None, + 'r': None, 'v': None, 'des': None} + else: + latest_field = None + + elif latest_field and latest_field in ('p', 'c', 'd'): + if latest_field == 'p': + for w in l.split(' '): if w: word = w.strip() data['p'].add(word) @@ -641,52 +688,10 @@ def map_updates_data(pkgs: Iterable[str], files: bool = False) -> dict: if word_split[0] != word: data['p'].add(word_split[0]) - elif field == 'Depends On': - val = val.strip() - - if val == 'None': - data['d'] = None else: - data['d'] = {w.strip() for w in val.split(' ') if w} - latest_field = 'd' - elif field == 'Conflicts With': - if val == 'None': - data['c'] = None - else: - data['c'] = {w.strip() for w in val.split(' ') if w} + data[latest_field].update((w.strip() for w in l.split(' ') if w)) - latest_field = 'c' - elif field == 'Download Size': - size = val.split(' ') - data['ds'] = size_to_byte(size[0], size[1]) - latest_field = 'ds' - elif field == 'Installed Size': - size = val.split(' ') - data['s'] = size_to_byte(size[0], size[1]) - latest_field = 's' - elif latest_name and latest_field == 's': - res[latest_name] = data - latest_name = None - latest_field = None - data = {'ds': None, 's': None, 'c': None, 'p': None, 'd': None, 'r': None, 'v': None} - else: - latest_field = None - - elif latest_field and latest_field in ('p', 'c', 'd'): - if latest_field == 'p': - for w in l.split(' '): - if w: - word = w.strip() - data['p'].add(word) - - word_split = word.split('=') - - if word_split[0] != word: - data['p'].add(word_split[0]) - else: - data[latest_field].update((w.strip() for w in l.split(' ') if w)) - - return res + return res def upgrade_several(pkgnames: Iterable[str], root_password: Optional[str], overwrite_conflicting_files: bool = False, skip_dependency_checks: bool = False) -> SimpleProcess: