diff --git a/CHANGELOG.md b/CHANGELOG.md index c6a78e52..c08fb233 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Features - Tray - displaying a notification when there is a new bauh release +- Arch + - allowing to uninstall no longer needed packages after a package uninstall [#87](https://github.com/vinifmor/bauh/issues/87) ### Improvements - Internet availability checking code diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index 0a26d65d..45dc7ac1 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -798,7 +798,54 @@ class ArchManager(SoftwareManager): return res - def _uninstall(self, context: TransactionContext): + def _request_uninstall_confirmation(self, pkgs: Iterable[str], context: TransactionContext) -> bool: + reqs = [InputOption(label=p, value=p, icon_path=get_icon_path(), read_only=True) for p in pkgs] + reqs_select = MultipleSelectComponent(options=reqs, default_options=set(reqs), label="", max_per_line=3) + + msg = '

{}

{}

'.format(self.i18n['arch.uninstall.required_by'].format(bold(context.name), bold(str(len(reqs)))), + self.i18n['arch.uninstall.required_by.advice']) + + if not context.watcher.request_confirmation(title=self.i18n['confirmation'].capitalize(), + body=msg, + components=[reqs_select], + confirmation_label=self.i18n['proceed'].capitalize(), + deny_label=self.i18n['cancel'].capitalize()): + context.watcher.print("Aborted") + return False + + return True + + def _request_unncessary_uninstall_confirmation(self, pkgs: Iterable[str], context: TransactionContext) -> List[str]: + 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']) + + 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()): + return + + return reqs_select.get_selected_values() + + def _request_all_unncessary_uninstall_confirmation(self, pkgs: Iterable[str], context: TransactionContext): + reqs = [InputOption(label=p, value=p, icon_path=get_icon_path(), read_only=True) for p in pkgs] + reqs_select = MultipleSelectComponent(options=reqs, default_options=set(reqs), label="", max_per_line=3) + + if not context.watcher.request_confirmation(title=self.i18n['confirmation'].capitalize(), + body=self.i18n['arch.uninstall.unnecessary.all'].format(len(pkgs)), + components=[reqs_select], + confirmation_label=self.i18n['proceed'].capitalize(), + deny_label=self.i18n['cancel'].capitalize()): + context.watcher.print("Aborted") + return False + + return True + + def _uninstall(self, context: TransactionContext, remove_unneeded: bool = False): self._update_progress(context, 10) required_by = self.deps_analyser.map_all_required_by({context.name}, set()) @@ -811,25 +858,59 @@ class ArchManager(SoftwareManager): if required_by: to_uninstall.update(required_by) - reqs = [InputOption(label=p, value=p, icon_path=get_icon_path(), read_only=True) for p in required_by] - reqs_select = MultipleSelectComponent(options=reqs, default_options=set(reqs), label="", max_per_line=3) - - msg = '

{}

{}

'.format(self.i18n['arch.uninstall.required_by'].format(bold(context.name), bold(str(len(reqs)))), - self.i18n['arch.uninstall.required_by.advice']) - - if not context.watcher.request_confirmation(title=self.i18n['confirmation'].capitalize(), - body=msg, - components=[reqs_select], - confirmation_label=self.i18n['proceed'].capitalize(), - deny_label=self.i18n['cancel'].capitalize()): - context.watcher.print("Aborted") + if not self._request_uninstall_confirmation(required_by, context): 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 + else: + optdeps = None + uninstalled = self._uninstall_pkgs(to_uninstall, context.root_password, context.handler) if uninstalled: self._update_progress(context, 70) + if optdeps: + context.watcher.change_substatus(self.i18n['arch.checking_unnecessary_deps']) + all_opt_deps = set() + + all_provided = pacman.map_provided(remote=False) + for deps in optdeps.values(): + for dep in deps.keys(): + real_deps = all_provided.get(dep) + + if real_deps: + all_opt_deps.update(real_deps) + + if all_opt_deps: + self.logger.info("Mapping optdeps required packages") + optdeps_reqs = pacman.map_required_by(all_opt_deps) + + no_longer_needed = set() + if optdeps_reqs: + for dep, reqs in optdeps_reqs.items(): + if not reqs: + no_longer_needed.add(dep) + + if no_longer_needed: + self.logger.info("{} packages no longer needed found".format(len(no_longer_needed))) + opt_to_uninstall = self._request_unncessary_uninstall_confirmation(no_longer_needed, context) + + if opt_to_uninstall: + opt_required = self.deps_analyser.map_all_required_by(opt_to_uninstall, set()) + unnecessary_to_uninstall = {*opt_to_uninstall, *opt_required} + + if len(opt_to_uninstall) == len(unnecessary_to_uninstall) or self._request_all_unncessary_uninstall_confirmation(unnecessary_to_uninstall, context): + unneded_uninstalled = self._uninstall_pkgs(unnecessary_to_uninstall, context.root_password, context.handler) + + if unneded_uninstalled: + to_uninstall.update(unnecessary_to_uninstall) + else: + self.logger.error("Could not uninstall some unnecessary packages") + context.watcher.print("Could not uninstall some unnecessary packages") + + self._update_progress(context, 90) if bool(context.config['clean_cached']): # cleaning old versions context.watcher.change_substatus(self.i18n['arch.uninstall.clean_cached.substatus']) if os.path.isdir('/var/cache/pacman/pkg'): @@ -857,7 +938,8 @@ class ArchManager(SoftwareManager): arch_config=read_config(), watcher=watcher, root_password=root_password, - handler=handler)) + handler=handler), + remove_unneeded=True) def get_managed_types(self) -> Set["type"]: return {ArchPackage} diff --git a/bauh/gems/arch/resources/locale/ca b/bauh/gems/arch/resources/locale/ca index d7e0feff..b796abb1 100644 --- a/bauh/gems/arch/resources/locale/ca +++ b/bauh/gems/arch/resources/locale/ca @@ -148,6 +148,7 @@ arch.install.optdeps.request.help=Marqueu els que voleu arch.install.optdeps.request.title=Dependències opcionals arch.install.repo_pkg.error.aur_deps=It is not possible to install an AUR package as dependency of a repository package arch.installing.package=S’està instal·lant el paquet {} +arch.checking_unnecessary_deps=Checking if there are packages no longer needed arch.makepkg.optimizing=Optimitzant la recopilació arch.missing_deps.body=S’han d’instal·lar les {deps} dependències següents abans de continuar amb la instal·lació de {name} arch.missing_deps.title=Dependències mancants @@ -174,6 +175,11 @@ arch.uninstall.clean_cached.error=No s'ha pogut eliminar {} versions antigues qu arch.uninstall.clean_cached.substatus=Eliminació de versions antigues del disc arch.uninstall.required_by=No es pot desinstal·lar {} perquè és necessari per al funcionament dels paquets següents. 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.l2=Check those you want to uninstall +arch.uninstall.unnecessary.proceed=Uninstall arch.uninstalling.conflict=S’està suprimint el paquet conflictiu {} arch.uninstalling.conflict.fail=No s’ha pogut desinstal·lar el paquet conflictiu {} arch.update.disabled.tooltip=This package can oly be upgrade through the action "Quick system upgrade" diff --git a/bauh/gems/arch/resources/locale/de b/bauh/gems/arch/resources/locale/de index 89fa1c82..5c9b57de 100644 --- a/bauh/gems/arch/resources/locale/de +++ b/bauh/gems/arch/resources/locale/de @@ -148,6 +148,7 @@ arch.install.optdeps.request.help=Wähle entsprechende aus arch.install.optdeps.request.title=Optionale Abhängigkeiten arch.install.repo_pkg.error.aur_deps=It is not possible to install an AUR package as dependency of a repository package arch.installing.package=Paket {} installieren +arch.checking_unnecessary_deps=Checking if there are packages no longer needed arch.makepkg.optimizing=Optimiert die Zusammenstellung arch.missing_deps.body=Die folgenden {deps} Abhängigkeiten müssten installiert sein, bevor mit der {name} Installation fortgefahren werden kann arch.missing_deps.title=Fehlende Abhängigkeiten @@ -174,6 +175,11 @@ arch.uninstall.clean_cached.error=It was not possible to remove old {} versions arch.uninstall.clean_cached.substatus=Removing old versions from disk arch.uninstall.required_by={} konnte nicht deinstalliert werden, da es für die folgenden Pakete benötigt wird. 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.l2=Check those you want to uninstall +arch.uninstall.unnecessary.proceed=Uninstall arch.uninstalling.conflict={} deinstallieren arch.uninstalling.conflict.fail=Deinstallation von {} fehlgeschlagen arch.update.disabled.tooltip=This package can oly be upgrade through the action "Quick system upgrade" diff --git a/bauh/gems/arch/resources/locale/en b/bauh/gems/arch/resources/locale/en index e14f6159..497a5504 100644 --- a/bauh/gems/arch/resources/locale/en +++ b/bauh/gems/arch/resources/locale/en @@ -148,6 +148,7 @@ arch.install.optdeps.request.help=Check those you want arch.install.optdeps.request.title=Optional dependencies arch.install.repo_pkg.error.aur_deps=It is not possible to install an AUR package as dependency of a repository package arch.installing.package=Installing {} package +arch.checking_unnecessary_deps=Checking if there are packages no longer needed arch.makepkg.optimizing=Optimizing the compilation arch.missing_deps.body=The following {deps} dependencies must be installed so the {name} installation can continue arch.missing_deps.title=Missing dependencies @@ -174,6 +175,11 @@ arch.uninstall.clean_cached.error=It was not possible to remove old {} versions arch.uninstall.clean_cached.substatus=Removing old versions from disk arch.uninstall.required_by={} cannot be uninstalled because it is required for the packages listed below to work. 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.l2=Check those you want to uninstall +arch.uninstall.unnecessary.proceed=Uninstall arch.uninstalling.conflict=Uninstalling conflicting package {} arch.uninstalling.conflict.fail=It was not possible to uninstall the conflicting package {} arch.update.disabled.tooltip=This package can oly be upgrade through the action "Quick system upgrade" diff --git a/bauh/gems/arch/resources/locale/es b/bauh/gems/arch/resources/locale/es index 34e71c71..bb72584c 100644 --- a/bauh/gems/arch/resources/locale/es +++ b/bauh/gems/arch/resources/locale/es @@ -148,6 +148,7 @@ arch.install.optdeps.request.help=Marque los que desee arch.install.optdeps.request.title=Dependencias opcionales arch.install.repo_pkg.error.aur_deps=It is not possible to install an AUR package as dependency of a repository package arch.installing.package=Instalando el paquete {} +arch.checking_unnecessary_deps=Verificando se hay paquetes innecesarios arch.makepkg.optimizing=Optimizing the compilation arch.missing_deps.body=Deben instalarse las siguientes {deps} dependencias para que la instalación de {name} pueda continuar arch.missing_deps.title=Dependencias faltantes @@ -174,6 +175,11 @@ arch.uninstall.clean_cached.error=No fue posible eliminar versiones antiguas de arch.uninstall.clean_cached.substatus=Eliminando versiones antiguas del disco arch.uninstall.required_by=No se puede desinstalar {} porque es necesario para que los paquetes enumerados abajo funcionen. 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.l2=Seleccione aquellos que desea desinstalar +arch.uninstall.unnecessary.proceed=Desinstalar arch.uninstalling.conflict=Eliminando el paquete conflictivo {} arch.uninstalling.conflict.fail=No fue posible desinstalar el paquete conflictivo {} arch.update.disabled.tooltip=Solo es posible actualizar este paquete a través de la acción "Actualización rápida de sistema" diff --git a/bauh/gems/arch/resources/locale/it b/bauh/gems/arch/resources/locale/it index 575aa8be..acafcea9 100644 --- a/bauh/gems/arch/resources/locale/it +++ b/bauh/gems/arch/resources/locale/it @@ -148,6 +148,7 @@ arch.install.optdeps.request.help=Controlla quelli che vuoi arch.install.optdeps.request.title=Dipendenze opzionali arch.install.repo_pkg.error.aur_deps=It is not possible to install an AUR package as dependency of a repository package arch.installing.package=Installazione del pacchetto {} +arch.checking_unnecessary_deps=Checking if there are packages no longer needed arch.makepkg.optimizing=Ottimizzando la compilazione arch.missing_deps.body=Le seguenti {deps} dipendenze devono essere installate prima che l'installazione di {name} continui arch.missing_deps.title=Dipendenze mancanti @@ -174,6 +175,11 @@ arch.uninstall.clean_cached.error=Non è stato possibile rimuovere le vecchie {} arch.uninstall.clean_cached.substatus=Rimozione di versioni precedenti dal disco arch.uninstall.required_by={} non può essere disinstallato perché è necessario che i seguenti pacchetti funzionino. 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.l2=Check those you want to uninstall: +arch.uninstall.unnecessary.proceed=Uninstall arch.uninstalling.conflict=Disinstallazione del pacchetto in conflitto {} arch.uninstalling.conflict.fail=Non è stato possibile disinstallare il pacchetto in conflitto {} arch.update.disabled.tooltip=This package can oly be upgrade through the action "Quick system upgrade" diff --git a/bauh/gems/arch/resources/locale/pt b/bauh/gems/arch/resources/locale/pt index f3845574..a30914e8 100644 --- a/bauh/gems/arch/resources/locale/pt +++ b/bauh/gems/arch/resources/locale/pt @@ -148,6 +148,7 @@ arch.install.optdeps.request.help=Marque os desejados arch.install.optdeps.request.title=Dependências opcionais arch.install.repo_pkg.error.aur_deps=Não é possível instalar um pacote do AUR como dependência de um pacote de repositório arch.installing.package=Instalando o pacote {} +arch.checking_unnecessary_deps=Verificando se há pacotes não mais necessários arch.makepkg.optimizing=Otimizando a compilação arch.missing_deps.body=As seguintes {deps} dependências devem ser instaladas para que a instalação de {name} continue arch.missing_deps.title=Dependências ausentes @@ -174,6 +175,11 @@ arch.uninstall.clean_cached.error=Não foi possível remover versões antigas de arch.uninstall.clean_cached.substatus=Removendo versões antigas do disco arch.uninstall.required_by={} não pode ser desinstalado porque ele é necessário para o funcionamento dos pacotes listados abaixo. 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.l2=Selecione os desejados para desinstalação +arch.uninstall.unnecessary.proceed=Desinstalar arch.uninstalling.conflict=Desinstalando o pacote conflitante {} arch.uninstalling.conflict.fail=Não foi possível desinstalar o pacote conflitante {} arch.update.disabled.tooltip=Este pacote só pode ser atualizado através da ação "Atualização rápida de sistema" diff --git a/bauh/gems/arch/resources/locale/ru b/bauh/gems/arch/resources/locale/ru index 3a81f43c..004db345 100644 --- a/bauh/gems/arch/resources/locale/ru +++ b/bauh/gems/arch/resources/locale/ru @@ -148,6 +148,7 @@ arch.install.optdeps.request.help=Отметьте те, которые вы х arch.install.optdeps.request.title=Необязательные зависимости arch.install.repo_pkg.error.aur_deps=It is not possible to install an AUR package as dependency of a repository package arch.installing.package=Установка пакета {} +arch.checking_unnecessary_deps=Checking if there are packages no longer needed arch.makepkg.optimizing=Оптимизация компиляции arch.missing_deps.body=Необходимо установить следующие зависимости , чтобы продолжить установку {name} arch.missing_deps.title=Отсутствующие зависимости @@ -174,6 +175,11 @@ arch.uninstall.clean_cached.error=It was not possible to remove old {} versions arch.uninstall.clean_cached.substatus=Removing old versions from disk 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.l2=Check those you want to uninstall +arch.uninstall.unnecessary.proceed=Uninstall arch.uninstalling.conflict=Удаление конфликтующего пакета {} arch.uninstalling.conflict.fail=Не удалось удалить конфликтующий пакет {} arch.update.disabled.tooltip=Этот пакет может быть обновлен только через действие "обновить систему"