[fix][arch][uninstall] not displaying removing all unnecessary dependencies

This commit is contained in:
Vinicius Moreira
2020-04-22 12:21:21 -03:00
parent 67542bdca9
commit 700a6ca9ce
13 changed files with 95 additions and 25 deletions

View File

@@ -17,7 +17,8 @@ class ProcessWatcher:
""" """
pass 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. request a user confirmation. In the current GUI implementation, it shows a popup to the user.
:param title: popup title :param title: popup title
@@ -26,6 +27,7 @@ class ProcessWatcher:
:param confirmation_label: optional confirmation button label (default to 'yes') :param confirmation_label: optional confirmation button label (default to 'yes')
:param deny_label: optional deny button label (default to 'no') :param deny_label: optional deny button label (default to 'no')
:param deny_button: if the deny button should be displayed :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 :return: if the request was confirmed by the user
""" """
pass pass

View File

@@ -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 = [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) 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)))), msg = '<p>{}</p><p>{}:</p>'.format(self.i18n['arch.uninstall.unnecessary.l1'].format(bold(context.name)),
self.i18n['arch.uninstall.unnecessary.l2']) self.i18n['arch.uninstall.unnecessary.l2'])
if not context.watcher.request_confirmation(title=self.i18n['confirmation'].capitalize(), if not context.watcher.request_confirmation(title=self.i18n['confirmation'].capitalize(),
body=msg, body=msg,
components=[reqs_select], components=[reqs_select],
confirmation_label=self.i18n['arch.uninstall.unnecessary.proceed'].capitalize(), 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
return reqs_select.get_selected_values() return reqs_select.get_selected_values()
@@ -862,30 +863,30 @@ class ArchManager(SoftwareManager):
return False return False
if remove_unneeded: 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: else:
optdeps = None all_deps_map = None
uninstalled = self._uninstall_pkgs(to_uninstall, context.root_password, context.handler) uninstalled = self._uninstall_pkgs(to_uninstall, context.root_password, context.handler)
if uninstalled: if uninstalled:
self._update_progress(context, 70) self._update_progress(context, 70)
if optdeps: if all_deps_map:
context.watcher.change_substatus(self.i18n['arch.checking_unnecessary_deps']) context.watcher.change_substatus(self.i18n['arch.checking_unnecessary_deps'])
all_opt_deps = set() all_deps = set()
all_provided = pacman.map_provided(remote=False) all_provided = pacman.map_provided(remote=False)
for deps in optdeps.values(): for deps in all_deps_map.values():
for dep in deps.keys(): for dep in deps:
real_deps = all_provided.get(dep) real_deps = all_provided.get(dep)
if real_deps: if real_deps:
all_opt_deps.update(real_deps) all_deps.update(real_deps)
if all_opt_deps: if all_deps:
self.logger.info("Mapping optdeps required packages") self.logger.info("Mapping dependencies required packages of uninstalled packages")
optdeps_reqs = pacman.map_required_by(all_opt_deps) optdeps_reqs = pacman.map_required_by(all_deps)
no_longer_needed = set() no_longer_needed = set()
if optdeps_reqs: if optdeps_reqs:

View File

@@ -760,6 +760,67 @@ def map_optional_deps(names: Iterable[str], remote: bool, not_installed: bool =
return res 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: def get_cache_dir() -> str:
dir_pattern = re.compile(r'.*CacheDir\s*=\s*.+') dir_pattern = re.compile(r'.*CacheDir\s*=\s*.+')

View File

@@ -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.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.all=The following {} packages will be uninstalled
arch.uninstall.unnecessary.cancel=Keep 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.l2=Check those you want to uninstall
arch.uninstall.unnecessary.proceed=Uninstall arch.uninstall.unnecessary.proceed=Uninstall
arch.uninstalling.conflict=Sestà suprimint el paquet conflictiu {} arch.uninstalling.conflict=Sestà suprimint el paquet conflictiu {}

View File

@@ -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.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.all=The following {} packages will be uninstalled
arch.uninstall.unnecessary.cancel=Keep 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.l2=Check those you want to uninstall
arch.uninstall.unnecessary.proceed=Uninstall arch.uninstall.unnecessary.proceed=Uninstall
arch.uninstalling.conflict={} deinstallieren arch.uninstalling.conflict={} deinstallieren

View File

@@ -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.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.all=The following {} packages will be uninstalled
arch.uninstall.unnecessary.cancel=Keep 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.l2=Check those you want to uninstall
arch.uninstall.unnecessary.proceed=Uninstall arch.uninstall.unnecessary.proceed=Uninstall
arch.uninstalling.conflict=Uninstalling conflicting package {} arch.uninstalling.conflict=Uninstalling conflicting package {}

View File

@@ -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.required_by.advice=Es necesario desinstalarlos también para continuar.
arch.uninstall.unnecessary.all=Los {} seguintes paquetes serán desinstalados arch.uninstall.unnecessary.all=Los {} seguintes paquetes serán desinstalados
arch.uninstall.unnecessary.cancel=Mantener 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.l2=Seleccione aquellos que desea desinstalar
arch.uninstall.unnecessary.proceed=Desinstalar arch.uninstall.unnecessary.proceed=Desinstalar
arch.uninstalling.conflict=Eliminando el paquete conflictivo {} arch.uninstalling.conflict=Eliminando el paquete conflictivo {}

View File

@@ -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.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.all=The following {} packages will be uninstalled
arch.uninstall.unnecessary.cancel=Keep 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.l2=Check those you want to uninstall:
arch.uninstall.unnecessary.proceed=Uninstall arch.uninstall.unnecessary.proceed=Uninstall
arch.uninstalling.conflict=Disinstallazione del pacchetto in conflitto {} arch.uninstalling.conflict=Disinstallazione del pacchetto in conflitto {}

View File

@@ -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.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.all=Os seguintes {} pacotes serão desinstalados
arch.uninstall.unnecessary.cancel=Manter 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.l2=Selecione os desejados para desinstalação
arch.uninstall.unnecessary.proceed=Desinstalar arch.uninstall.unnecessary.proceed=Desinstalar
arch.uninstalling.conflict=Desinstalando o pacote conflitante {} arch.uninstalling.conflict=Desinstalando o pacote conflitante {}

View File

@@ -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.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.all=The following {} packages will be uninstalled
arch.uninstall.unnecessary.cancel=Keep 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.l2=Check those you want to uninstall
arch.uninstall.unnecessary.proceed=Uninstall arch.uninstall.unnecessary.proceed=Uninstall
arch.uninstalling.conflict=Удаление конфликтующего пакета {} arch.uninstalling.conflict=Удаление конфликтующего пакета {}

View File

@@ -1,6 +1,6 @@
from typing import List 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 PyQt5.QtWidgets import QMessageBox, QVBoxLayout, QLabel, QWidget, QScrollArea, QFrame
from bauh.api.abstract.view import ViewComponent from bauh.api.abstract.view import ViewComponent
@@ -12,8 +12,12 @@ from bauh.view.util.translation import I18n
class ConfirmationDialog(QMessageBox): class ConfirmationDialog(QMessageBox):
def __init__(self, title: str, body: str, i18n: I18n, screen_size: QSize, components: List[ViewComponent] = None, 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__() super(ConfirmationDialog, self).__init__()
if not window_cancel:
self.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowTitleHint)
self.setWindowTitle(title) self.setWindowTitle(title)
self.setStyleSheet('QLabel { margin-right: 25px; }') 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) self.bt_yes = self.addButton(i18n['popup.button.yes'] if not confirmation_label else confirmation_label.capitalize(), QMessageBox.YesRole)

View File

@@ -49,9 +49,10 @@ class AsyncAction(QThread, ProcessWatcher):
self.root_password = None self.root_password = None
self.stop = False 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.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() self.wait_user()
return self.confirmation_res return self.confirmation_res

View File

@@ -445,6 +445,7 @@ class ManageWindow(QWidget):
confirmation_label=msg['confirmation_label'], confirmation_label=msg['confirmation_label'],
deny_label=msg['deny_label'], deny_label=msg['deny_label'],
deny_button=msg['deny_button'], deny_button=msg['deny_button'],
window_cancel=msg['window_cancel'],
screen_size=self.screen_size) screen_size=self.screen_size)
res = diag.is_confirmed() res = diag.is_confirmed()
self.thread_animate_progress.animate() self.thread_animate_progress.animate()