diff --git a/bauh/api/abstract/handler.py b/bauh/api/abstract/handler.py index cfbf4a4e..3fe9a9d1 100644 --- a/bauh/api/abstract/handler.py +++ b/bauh/api/abstract/handler.py @@ -17,7 +17,8 @@ class ProcessWatcher: """ pass - def request_confirmation(self, title: str, body: str, components: List[ViewComponent] = None, confirmation_label: str = None, deny_label: str = None, deny_button: bool = True)-> bool: + def request_confirmation(self, title: str, body: str, components: List[ViewComponent] = None, confirmation_label: str = None, + deny_label: str = None, deny_button: bool = True, window_cancel: bool = True) -> bool: """ request a user confirmation. In the current GUI implementation, it shows a popup to the user. :param title: popup title @@ -26,6 +27,7 @@ class ProcessWatcher: :param confirmation_label: optional confirmation button label (default to 'yes') :param deny_label: optional deny button label (default to 'no') :param deny_button: if the deny button should be displayed + :param window_cancel: if the window cancel button should be visible :return: if the request was confirmed by the user """ pass diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index 45dc7ac1..7bb1ee9a 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -819,14 +819,15 @@ class ArchManager(SoftwareManager): reqs = [InputOption(label=p, value=p, icon_path=get_icon_path(), read_only=False) for p in pkgs] reqs_select = MultipleSelectComponent(options=reqs, default_options=set(reqs), label="", max_per_line=3) - msg = '{}. {}:'.format(self.i18n['arch.uninstall.unnecessary.l1'].format(bold(str(len(reqs)))), - self.i18n['arch.uninstall.unnecessary.l2']) + msg = '
{}
{}:
'.format(self.i18n['arch.uninstall.unnecessary.l1'].format(bold(context.name)), + self.i18n['arch.uninstall.unnecessary.l2']) if not context.watcher.request_confirmation(title=self.i18n['confirmation'].capitalize(), body=msg, components=[reqs_select], confirmation_label=self.i18n['arch.uninstall.unnecessary.proceed'].capitalize(), - deny_label=self.i18n['arch.uninstall.unnecessary.cancel'].capitalize()): + deny_label=self.i18n['arch.uninstall.unnecessary.cancel'].capitalize(), + window_cancel=False): return return reqs_select.get_selected_values() @@ -862,30 +863,30 @@ class ArchManager(SoftwareManager): return False if remove_unneeded: - optdeps = pacman.map_optional_deps(names=to_uninstall, remote=False) # retrieving the optdeps to check if they are still necessary + all_deps_map = pacman.map_all_deps(names=to_uninstall, only_installed=True) # retrieving the deps to check if they are still necessary else: - optdeps = None + all_deps_map = None uninstalled = self._uninstall_pkgs(to_uninstall, context.root_password, context.handler) if uninstalled: self._update_progress(context, 70) - if optdeps: + if all_deps_map: context.watcher.change_substatus(self.i18n['arch.checking_unnecessary_deps']) - all_opt_deps = set() + all_deps = set() all_provided = pacman.map_provided(remote=False) - for deps in optdeps.values(): - for dep in deps.keys(): + for deps in all_deps_map.values(): + for dep in deps: real_deps = all_provided.get(dep) if real_deps: - all_opt_deps.update(real_deps) + all_deps.update(real_deps) - if all_opt_deps: - self.logger.info("Mapping optdeps required packages") - optdeps_reqs = pacman.map_required_by(all_opt_deps) + if all_deps: + self.logger.info("Mapping dependencies required packages of uninstalled packages") + optdeps_reqs = pacman.map_required_by(all_deps) no_longer_needed = set() if optdeps_reqs: diff --git a/bauh/gems/arch/pacman.py b/bauh/gems/arch/pacman.py index 9a8c2d3b..ced56a8f 100644 --- a/bauh/gems/arch/pacman.py +++ b/bauh/gems/arch/pacman.py @@ -760,6 +760,67 @@ def map_optional_deps(names: Iterable[str], remote: bool, not_installed: bool = return res +def map_all_deps(names: Iterable[str], only_installed: bool = False) -> Dict[str, Set[str]]: + output = run_cmd('pacman -Qi {}'.format(' '.join(names))) + + if output: + res = {} + deps_fields = {'Depends On', 'Optional Deps'} + latest_name, deps, latest_field = None, None, 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() + + if field == 'Name': + latest_field = field + val = line[field_sep_idx + 1:].strip() + latest_name = val + deps = None + elif field in deps_fields: + latest_field = field + val = line[field_sep_idx + 1:].strip() + opt_deps = latest_field == 'Optional Deps' + + if deps is None: + deps = set() + + if val != 'None': + if ':' in val: + dep_info = val.split(':') + desc = dep_info[1].strip() + + if desc and opt_deps and only_installed and '[installed]' not in desc: + continue + + deps.add(dep_info[0].strip()) + else: + deps.update({dep.strip() for dep in val.split(' ') if dep}) + + elif latest_name and deps is not None: + res[latest_name] = deps + latest_name, deps, latest_field = None, None, None + + elif latest_name and deps is not None: + opt_deps = latest_field == 'Optional Deps' + + if ':' in l: + dep_info = l.split(':') + desc = dep_info[1].strip() + + if desc and opt_deps and only_installed and '[installed]' not in desc: + continue + + deps.add(dep_info[0].strip()) + else: + deps.update({dep.strip() for dep in l.split(' ') if dep}) + + return res + + def get_cache_dir() -> str: dir_pattern = re.compile(r'.*CacheDir\s*=\s*.+') diff --git a/bauh/gems/arch/resources/locale/ca b/bauh/gems/arch/resources/locale/ca index b796abb1..8586a29d 100644 --- a/bauh/gems/arch/resources/locale/ca +++ b/bauh/gems/arch/resources/locale/ca @@ -177,7 +177,7 @@ arch.uninstall.required_by=No es pot desinstal·lar {} perquè és necessari per arch.uninstall.required_by.advice=It is necessary to uninstall them as well to proceed. arch.uninstall.unnecessary.all=The following {} packages will be uninstalled arch.uninstall.unnecessary.cancel=Keep -arch.uninstall.unnecessary.l1=The {} packages below are no longer needed +arch.uninstall.unnecessary.l1={} was successfully uninstalled. The packages below that he depended on are no longer needed. arch.uninstall.unnecessary.l2=Check those you want to uninstall arch.uninstall.unnecessary.proceed=Uninstall arch.uninstalling.conflict=S’està suprimint el paquet conflictiu {} diff --git a/bauh/gems/arch/resources/locale/de b/bauh/gems/arch/resources/locale/de index 5c9b57de..c9a0bfff 100644 --- a/bauh/gems/arch/resources/locale/de +++ b/bauh/gems/arch/resources/locale/de @@ -177,7 +177,7 @@ arch.uninstall.required_by={} konnte nicht deinstalliert werden, da es für die arch.uninstall.required_by.advice=It is necessary to uninstall them as well to proceed. arch.uninstall.unnecessary.all=The following {} packages will be uninstalled arch.uninstall.unnecessary.cancel=Keep -arch.uninstall.unnecessary.l1=The {} packages below are no longer needed +arch.uninstall.unnecessary.l1={} was successfully uninstalled. The packages below that he depended on are no longer needed. arch.uninstall.unnecessary.l2=Check those you want to uninstall arch.uninstall.unnecessary.proceed=Uninstall arch.uninstalling.conflict={} deinstallieren diff --git a/bauh/gems/arch/resources/locale/en b/bauh/gems/arch/resources/locale/en index 497a5504..b1d1710f 100644 --- a/bauh/gems/arch/resources/locale/en +++ b/bauh/gems/arch/resources/locale/en @@ -177,7 +177,7 @@ arch.uninstall.required_by={} cannot be uninstalled because it is required for t arch.uninstall.required_by.advice=It is necessary to uninstall them as well to proceed. arch.uninstall.unnecessary.all=The following {} packages will be uninstalled arch.uninstall.unnecessary.cancel=Keep -arch.uninstall.unnecessary.l1=The {} packages below are no longer needed +arch.uninstall.unnecessary.l1={} was successfully uninstalled. The packages below that he depended on are no longer needed. arch.uninstall.unnecessary.l2=Check those you want to uninstall arch.uninstall.unnecessary.proceed=Uninstall arch.uninstalling.conflict=Uninstalling conflicting package {} diff --git a/bauh/gems/arch/resources/locale/es b/bauh/gems/arch/resources/locale/es index bb72584c..81e1bf6a 100644 --- a/bauh/gems/arch/resources/locale/es +++ b/bauh/gems/arch/resources/locale/es @@ -177,7 +177,7 @@ arch.uninstall.required_by=No se puede desinstalar {} porque es necesario para q arch.uninstall.required_by.advice=Es necesario desinstalarlos también para continuar. arch.uninstall.unnecessary.all=Los {} seguintes paquetes serán desinstalados arch.uninstall.unnecessary.cancel=Mantener -arch.uninstall.unnecessary.l1=Los {} paquetes abajo ya no son necesarios +arch.uninstall.unnecessary.l1={} se desinstaló correctamente. Los paquetes abajo de los que dependía ya no son necesarios arch.uninstall.unnecessary.l2=Seleccione aquellos que desea desinstalar arch.uninstall.unnecessary.proceed=Desinstalar arch.uninstalling.conflict=Eliminando el paquete conflictivo {} diff --git a/bauh/gems/arch/resources/locale/it b/bauh/gems/arch/resources/locale/it index acafcea9..34752fc3 100644 --- a/bauh/gems/arch/resources/locale/it +++ b/bauh/gems/arch/resources/locale/it @@ -177,7 +177,7 @@ arch.uninstall.required_by={} non può essere disinstallato perché è necessari arch.uninstall.required_by.advice=It is necessary to uninstall them as well to proceed. arch.uninstall.unnecessary.all=The following {} packages will be uninstalled arch.uninstall.unnecessary.cancel=Keep -arch.uninstall.unnecessary.l1=The {} packages below are no longer needed +arch.uninstall.unnecessary.l1={} was successfully uninstalled. The packages below that he depended on are no longer needed. arch.uninstall.unnecessary.l2=Check those you want to uninstall: arch.uninstall.unnecessary.proceed=Uninstall arch.uninstalling.conflict=Disinstallazione del pacchetto in conflitto {} diff --git a/bauh/gems/arch/resources/locale/pt b/bauh/gems/arch/resources/locale/pt index a30914e8..6a740b45 100644 --- a/bauh/gems/arch/resources/locale/pt +++ b/bauh/gems/arch/resources/locale/pt @@ -177,7 +177,7 @@ arch.uninstall.required_by={} não pode ser desinstalado porque ele é necessár arch.uninstall.required_by.advice=Para prosseguir será necessário desinstá-los também. arch.uninstall.unnecessary.all=Os seguintes {} pacotes serão desinstalados arch.uninstall.unnecessary.cancel=Manter -arch.uninstall.unnecessary.l1=Os {} pacotes abaixo não são mais necessários. +arch.uninstall.unnecessary.l1={} foi desinstalado com sucesso ! Os pacotes abaixo que ele dependia já não são mais necessários. arch.uninstall.unnecessary.l2=Selecione os desejados para desinstalação arch.uninstall.unnecessary.proceed=Desinstalar arch.uninstalling.conflict=Desinstalando o pacote conflitante {} diff --git a/bauh/gems/arch/resources/locale/ru b/bauh/gems/arch/resources/locale/ru index 004db345..823804f1 100644 --- a/bauh/gems/arch/resources/locale/ru +++ b/bauh/gems/arch/resources/locale/ru @@ -177,7 +177,7 @@ arch.uninstall.required_by={} не может быть удален, так ка arch.uninstall.required_by.advice=It is necessary to uninstall them as well to proceed. arch.uninstall.unnecessary.all=The following {} packages will be uninstalled arch.uninstall.unnecessary.cancel=Keep -arch.uninstall.unnecessary.l1=The {} packages below are no longer needed +arch.uninstall.unnecessary.l1={} was successfully uninstalled. The packages below that he depended on are no longer needed. arch.uninstall.unnecessary.l2=Check those you want to uninstall arch.uninstall.unnecessary.proceed=Uninstall arch.uninstalling.conflict=Удаление конфликтующего пакета {} diff --git a/bauh/view/qt/confirmation.py b/bauh/view/qt/confirmation.py index 29936cba..4d30cd97 100644 --- a/bauh/view/qt/confirmation.py +++ b/bauh/view/qt/confirmation.py @@ -1,6 +1,6 @@ from typing import List -from PyQt5.QtCore import QSize +from PyQt5.QtCore import QSize, Qt from PyQt5.QtWidgets import QMessageBox, QVBoxLayout, QLabel, QWidget, QScrollArea, QFrame from bauh.api.abstract.view import ViewComponent @@ -12,8 +12,12 @@ from bauh.view.util.translation import I18n class ConfirmationDialog(QMessageBox): def __init__(self, title: str, body: str, i18n: I18n, screen_size: QSize, components: List[ViewComponent] = None, - confirmation_label: str = None, deny_label: str = None, deny_button: bool = True): + confirmation_label: str = None, deny_label: str = None, deny_button: bool = True, window_cancel: bool = True): super(ConfirmationDialog, self).__init__() + + if not window_cancel: + self.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowTitleHint) + self.setWindowTitle(title) self.setStyleSheet('QLabel { margin-right: 25px; }') self.bt_yes = self.addButton(i18n['popup.button.yes'] if not confirmation_label else confirmation_label.capitalize(), QMessageBox.YesRole) diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py index c0c3192b..8b708bd2 100644 --- a/bauh/view/qt/thread.py +++ b/bauh/view/qt/thread.py @@ -49,9 +49,10 @@ class AsyncAction(QThread, ProcessWatcher): self.root_password = None self.stop = False - def request_confirmation(self, title: str, body: str, components: List[ViewComponent] = None, confirmation_label: str = None, deny_label: str = None, deny_button: bool = True) -> bool: + def request_confirmation(self, title: str, body: str, components: List[ViewComponent] = None, + confirmation_label: str = None, deny_label: str = None, deny_button: bool = True, window_cancel: bool = True) -> bool: self.wait_confirmation = True - self.signal_confirmation.emit({'title': title, 'body': body, 'components': components, 'confirmation_label': confirmation_label, 'deny_label': deny_label, 'deny_button': deny_button}) + self.signal_confirmation.emit({'title': title, 'body': body, 'components': components, 'confirmation_label': confirmation_label, 'deny_label': deny_label, 'deny_button': deny_button, 'window_cancel': window_cancel}) self.wait_user() return self.confirmation_res diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py index cc72d826..bc9f5fc8 100755 --- a/bauh/view/qt/window.py +++ b/bauh/view/qt/window.py @@ -445,6 +445,7 @@ class ManageWindow(QWidget): confirmation_label=msg['confirmation_label'], deny_label=msg['deny_label'], deny_button=msg['deny_button'], + window_cancel=msg['window_cancel'], screen_size=self.screen_size) res = diag.is_confirmed() self.thread_animate_progress.animate()