[ui][update] displaying requirements and upgrade order

This commit is contained in:
Vinicius Moreira
2020-02-10 12:42:53 -03:00
parent 684ff621d0
commit 3e956a5413
11 changed files with 188 additions and 56 deletions

View File

@@ -59,7 +59,7 @@ class ArchManager(SoftwareManager):
self.aur_cache = context.cache_factory.new() self.aur_cache = context.cache_factory.new()
# context.disk_loader_factory.map(ArchPackage, self.aur_cache) TODO # context.disk_loader_factory.map(ArchPackage, self.aur_cache) TODO
self.mapper = ArchDataMapper(http_client=context.http_client) self.mapper = ArchDataMapper(http_client=context.http_client, i18n=context.i18n)
self.i18n = context.i18n self.i18n = context.i18n
self.aur_client = AURClient(context.http_client, context.logger) self.aur_client = AURClient(context.http_client, context.logger)
self.dcache_updater = ArchDiskCacheUpdater(context.logger, context.disk_cache) self.dcache_updater = ArchDiskCacheUpdater(context.logger, context.disk_cache)
@@ -157,7 +157,7 @@ class ArchManager(SoftwareManager):
for name, data in not_signed.items(): for name, data in not_signed.items():
pkg = ArchPackage(name=name, version=data.get('version'), pkg = ArchPackage(name=name, version=data.get('version'),
latest_version=data.get('version'), description=data.get('description'), latest_version=data.get('version'), description=data.get('description'),
installed=True, mirror='aur') installed=True, mirror='aur', i18n=self.i18n)
pkg.categories = self.categories.get(pkg.name) pkg.categories = self.categories.get(pkg.name)
pkg.downgrade_enabled = downgrade_enabled pkg.downgrade_enabled = downgrade_enabled
@@ -171,7 +171,7 @@ class ArchManager(SoftwareManager):
def _fill_mirror_pkgs(self, mirrors: dict, apps: list): def _fill_mirror_pkgs(self, mirrors: dict, apps: list):
# TODO # TODO
for name, data in mirrors.items(): for name, data in mirrors.items():
app = ArchPackage(name=name, version=data.get('version'), latest_version=data.get('version'), description=data.get('description')) app = ArchPackage(name=name, version=data.get('version'), latest_version=data.get('version'), description=data.get('description'), i18n=self.i18n)
app.installed = True app.installed = True
app.mirror = '' # TODO app.mirror = '' # TODO
app.update = False # TODO app.update = False # TODO
@@ -304,7 +304,7 @@ class ArchManager(SoftwareManager):
def get_info(self, pkg: ArchPackage) -> dict: def get_info(self, pkg: ArchPackage) -> dict:
if pkg.installed: if pkg.installed:
t = Thread(target=self.mapper.fill_package_build, args=(pkg,)) t = Thread(target=self.mapper.fill_package_build, args=(pkg,), daemon=True)
t.start() t.start()
info = pacman.get_info_dict(pkg.name) info = pacman.get_info_dict(pkg.name)
@@ -407,7 +407,7 @@ class ArchManager(SoftwareManager):
pkgbase = self.aur_client.get_src_info(dep[0])['pkgbase'] pkgbase = self.aur_client.get_src_info(dep[0])['pkgbase']
installed = self._install_from_aur(pkgname=dep[0], pkgbase=pkgbase, maintainer=None, root_password=root_password, handler=handler, dependency=True, change_progress=False) installed = self._install_from_aur(pkgname=dep[0], pkgbase=pkgbase, maintainer=None, root_password=root_password, handler=handler, dependency=True, change_progress=False)
else: else:
installed = self._install(pkgname=dep[0], maintainer=None, root_password=root_password, handler=handler, install_file=None, mirror=dep[1], change_progress=False) installed = self._install(pkgname=dep[0], maintainer=dep[1], root_password=root_password, handler=handler, install_file=None, mirror=dep[1], change_progress=False)
if not installed: if not installed:
return dep[0] return dep[0]
@@ -737,6 +737,8 @@ class ArchManager(SoftwareManager):
return False return False
def _install_from_aur(self, pkgname: str, pkgbase: str, maintainer: str, root_password: str, handler: ProcessHandler, dependency: bool, skip_optdeps: bool = False, change_progress: bool = True) -> bool: def _install_from_aur(self, pkgname: str, pkgbase: str, maintainer: str, root_password: str, handler: ProcessHandler, dependency: bool, skip_optdeps: bool = False, change_progress: bool = True) -> bool:
self._optimize_makepkg(watcher=handler.watcher)
app_build_dir = '{}/build_{}'.format(BUILD_DIR, int(time.time())) app_build_dir = '{}/build_{}'.format(BUILD_DIR, int(time.time()))
try: try:
@@ -831,9 +833,11 @@ class ArchManager(SoftwareManager):
handler = ProcessHandler(watcher) handler = ProcessHandler(watcher)
self._sync_databases(root_password=root_password, handler=handler) self._sync_databases(root_password=root_password, handler=handler)
self._optimize_makepkg(watcher=watcher)
res = self._install_from_aur(pkg.name, pkg.package_base, pkg.maintainer, root_password, handler, dependency=False, skip_optdeps=skip_optdeps) if pkg.mirror == 'aur':
res = self._install_from_aur(pkg.name, pkg.package_base, pkg.maintainer, root_password, handler, dependency=False, skip_optdeps=skip_optdeps)
else:
res = self._install(pkgname=pkg.name, maintainer=pkg.mirror, root_password=root_password, handler=handler, install_file=None, mirror=pkg.mirror, change_progress=False)
if res: if res:
if os.path.exists(pkg.get_disk_data_path()): if os.path.exists(pkg.get_disk_data_path()):
@@ -1017,7 +1021,7 @@ class ArchManager(SoftwareManager):
threads = [] threads = []
for pkg in pkgs: for pkg in pkgs:
t = Thread(target=_add_info, args=(pkg, )) t = Thread(target=_add_info, args=(pkg, ), daemon=True)
t.start() t.start()
threads.append(t) threads.append(t)
@@ -1082,38 +1086,45 @@ class ArchManager(SoftwareManager):
return sorted_names[pkg.name] return sorted_names[pkg.name]
def _map_and_add_package(self, pkg_data: Tuple[str, str], idx: int, output: dict):
version = None
if pkg_data[1] == 'aur':
try:
info = self.aur_client.get_src_info(pkg_data[0])
if info:
version = info.get('pkgver')
if not version:
self.logger.warning("No version declared in SRCINFO of '{}'".format(pkg_data[0]))
else:
self.logger.warning("Could not retrieve the SRCINFO for '{}'".format(pkg_data[0]))
except:
self.logger.warning("Could not retrieve the SRCINFO for '{}'".format(pkg_data[0]))
else:
version = pacman.get_version_for_not_installed(pkg_data[0])
output[idx] = ArchPackage(name=pkg_data[0], version=version, latest_version=version, mirror=pkg_data[1], i18n=self.i18n)
def get_update_requirements(self, pkgs: List[ArchPackage], watcher: ProcessWatcher) -> List[ArchPackage]: def get_update_requirements(self, pkgs: List[ArchPackage], watcher: ProcessWatcher) -> List[ArchPackage]:
deps = self._map_known_missing_deps({p.get_base_name(): 'aur' for p in pkgs}, watcher) deps = self._map_known_missing_deps({p.get_base_name(): 'aur' for p in pkgs}, watcher)
if deps: # filtering selected packages
selected_names = {p.name for p in pkgs}
deps = [dep for dep in deps if dep[0] not in selected_names]
if deps: if deps:
pkg_names = {p.name for p in pkgs} map_threads, sorted_pkgs = [], {}
return [ArchPackage(name=pkg[0]) for pkg in deps if pkg[0] not in pkg_names]
for idx, dep in enumerate(deps):
t = Thread(target=self._map_and_add_package, args=(dep, idx, sorted_pkgs), daemon=True)
t.start()
map_threads.append(t)
for t in map_threads:
t.join()
return [sorted_pkgs[idx] for idx in sorted(sorted_pkgs)]
else: else:
return [] return []
# def add_srcinfo(pkg: ArchPackage):
# try:
# srcinfo = self.aur_client.get_src_info(pkg.get_base_name())
# pkg.src_info = srcinfo
# pkg.dependencies = self.aur_client.extract_required_dependencies(pkg.src_info)
# except:
# self.logger.warning("Could not retrieve the SRCINFO for package {}".format(pkg.name))
#
# threads = []
#
# for p in pkgs:
# t = Thread(target=add_srcinfo, args=(p,))
# t.start()
# threads.append(t)
#
# for t in threads:
# t.join()
#
# for p in pkgs:
# if p.src_info and p.dependencies:
# self.inst
# else:
# self.logger.warning("Not checking update requirements for package {}".format(pkg.name))

View File

@@ -4,6 +4,7 @@ from datetime import datetime
from bauh.api.abstract.model import PackageStatus from bauh.api.abstract.model import PackageStatus
from bauh.api.http import HttpClient from bauh.api.http import HttpClient
from bauh.gems.arch.model import ArchPackage from bauh.gems.arch.model import ArchPackage
from bauh.view.util.translation import I18n
URL_PKG_DOWNLOAD = 'https://aur.archlinux.org/{}' URL_PKG_DOWNLOAD = 'https://aur.archlinux.org/{}'
RE_LETTERS = re.compile(r'\.([a-zA-Z]+)-\d+$') RE_LETTERS = re.compile(r'\.([a-zA-Z]+)-\d+$')
@@ -22,8 +23,9 @@ V_SUFFIX_MAP = {s: {'c': sfxs[0], 'p': idx} for idx, sfxs in enumerate([RE_SFX,
class ArchDataMapper: class ArchDataMapper:
def __init__(self, http_client: HttpClient): def __init__(self, http_client: HttpClient, i18n: I18n):
self.http_client = http_client self.http_client = http_client
self.i18n = i18n
def fill_api_data(self, pkg: ArchPackage, package: dict, fill_version: bool = True): def fill_api_data(self, pkg: ArchPackage, package: dict, fill_version: bool = True):
@@ -115,7 +117,7 @@ class ArchDataMapper:
def map_api_data(self, apidata: dict, installed: dict, categories: dict) -> ArchPackage: def map_api_data(self, apidata: dict, installed: dict, categories: dict) -> ArchPackage:
data = installed.get(apidata.get('Name')) data = installed.get(apidata.get('Name'))
app = ArchPackage(name=apidata.get('Name'), installed=bool(data), mirror='aur') app = ArchPackage(name=apidata.get('Name'), installed=bool(data), mirror='aur', i18n=self.i18n)
app.status = PackageStatus.LOADING_DATA app.status = PackageStatus.LOADING_DATA
if categories: if categories:

View File

@@ -4,6 +4,7 @@ from typing import List, Set
from bauh.api.abstract.model import SoftwarePackage from bauh.api.abstract.model import SoftwarePackage
from bauh.commons import resource from bauh.commons import resource
from bauh.gems.arch import ROOT_DIR, ARCH_CACHE_PATH from bauh.gems.arch import ROOT_DIR, ARCH_CACHE_PATH
from bauh.view.util.translation import I18n
CACHED_ATTRS = {'command', 'icon_path', 'mirror', 'maintainer', 'desktop_entry', 'categories'} CACHED_ATTRS = {'command', 'icon_path', 'mirror', 'maintainer', 'desktop_entry', 'categories'}
@@ -14,7 +15,8 @@ class ArchPackage(SoftwarePackage):
package_base: str = None, votes: int = None, popularity: float = None, package_base: str = None, votes: int = None, popularity: float = None,
first_submitted: datetime.datetime = None, last_modified: datetime.datetime = None, first_submitted: datetime.datetime = None, last_modified: datetime.datetime = None,
maintainer: str = None, url_download: str = None, pkgbuild: str = None, mirror: str = None, maintainer: str = None, url_download: str = None, pkgbuild: str = None, mirror: str = None,
desktop_entry: str = None, installed: bool = False, srcinfo: dict = None, dependencies: Set[str] = None): desktop_entry: str = None, installed: bool = False, srcinfo: dict = None, dependencies: Set[str] = None,
i18n: I18n = None):
super(ArchPackage, self).__init__(name=name, version=version, latest_version=latest_version, description=description, installed=installed) super(ArchPackage, self).__init__(name=name, version=version, latest_version=latest_version, description=description, installed=installed)
self.package_base = package_base self.package_base = package_base
@@ -32,6 +34,7 @@ class ArchPackage(SoftwarePackage):
self.desktop_entry = desktop_entry self.desktop_entry = desktop_entry
self.src_info = srcinfo self.src_info = srcinfo
self.dependencies = dependencies self.dependencies = dependencies
self.i18n = i18n
@staticmethod @staticmethod
def disk_cache_path(pkgname: str, mirror: str): def disk_cache_path(pkgname: str, mirror: str):
@@ -63,7 +66,7 @@ class ArchPackage(SoftwarePackage):
return self.icon_path return self.icon_path
def get_type_icon_path(self): def get_type_icon_path(self):
return resource.get_path('img/arch.svg', ROOT_DIR) # TODO change icon when from mirrors return resource.get_path('img/{}.svg'.format('arch' if self.mirror == 'aur' else 'mirror'), ROOT_DIR)
def is_application(self): def is_application(self):
return self.can_be_run() return self.can_be_run()
@@ -121,6 +124,9 @@ class ArchPackage(SoftwarePackage):
def has_screenshots(self) -> bool: def has_screenshots(self) -> bool:
return False return False
def get_name_tooltip(self) -> str:
return '{}: {}'.format(self.i18n['repository'], self.mirror)
def __str__(self): def __str__(self):
return self.__repr__() return self.__repr__()

View File

@@ -359,3 +359,10 @@ def read_dependencies(name: str) -> Set[str]:
def sync_databases(root_password: str, force: bool = False) -> SimpleProcess: def sync_databases(root_password: str, force: bool = False) -> SimpleProcess:
return SimpleProcess(cmd=['pacman', '-Sy{}'.format('y' if force else '')], return SimpleProcess(cmd=['pacman', '-Sy{}'.format('y' if force else '')],
root_password=root_password) root_password=root_password)
def get_version_for_not_installed(pkgname: str) -> str:
output = run_cmd('pacman -Ss {}'.format(pkgname), print_error=False)
if output:
return output.split('\n')[0].split(' ')[1].strip()

View File

@@ -1,3 +1,4 @@
import os
import re import re
import time import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
@@ -10,7 +11,7 @@ from bauh.api.abstract.cache import MemoryCache
from bauh.api.abstract.controller import SoftwareManager from bauh.api.abstract.controller import SoftwareManager
from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.handler import ProcessWatcher
from bauh.api.abstract.model import PackageStatus, SoftwarePackage, PackageAction from bauh.api.abstract.model import PackageStatus, SoftwarePackage, PackageAction
from bauh.api.abstract.view import InputViewComponent, MessageType from bauh.api.abstract.view import InputViewComponent, MessageType, MultipleSelectComponent, InputOption
from bauh.api.exception import NoInternetException from bauh.api.exception import NoInternetException
from bauh.view.core import config from bauh.view.core import config
from bauh.view.qt import commons from bauh.view.qt import commons
@@ -98,6 +99,69 @@ class UpdateSelectedApps(AsyncAction):
self.root_password = None self.root_password = None
self.i18n = i18n self.i18n = i18n
def _pkg_as_option(self, pkg: SoftwarePackage, tooltip: bool = True) -> InputOption:
if pkg.installed:
icon_path = pkg.get_disk_icon_path()
if not os.path.isfile(icon_path):
icon_path = pkg.get_type_icon_path()
else:
icon_path = pkg.get_type_icon_path()
return InputOption(label='{}{}'.format(pkg.name, ' ( {} )'.format(pkg.latest_version) if pkg.version else ''),
value=None,
tooltip=pkg.get_name_tooltip() if tooltip else None,
read_only=True,
icon_path=icon_path)
def _handle_update_requirements(self, pkgs: List[SoftwarePackage]) -> bool:
self.change_substatus(self.i18n['action.update.requirements.status'])
required_pkgs = self.manager.get_update_requirements(pkgs, self)
if required_pkgs:
opts = [self._pkg_as_option(p) for p in required_pkgs]
if not self.request_confirmation(self.i18n['action.update.requirements.title'],
self.i18n['action.update.requirements.body'],
[MultipleSelectComponent(label='', options=opts, default_options=set(opts))],
confirmation_label=self.i18n['continue'].capitalize(),
deny_label=self.i18n['cancel'].capitalize()):
self.notify_finished({'success': False, 'updated': 0, 'types': set()})
self.pkgs = None
return False
else:
for pkg in required_pkgs:
if not self.manager.install(pkg, self.root_password, self):
self.notify_finished({'success': False, 'updated': 0, 'types': set()})
self.pkgs = None
label = '{}{}'.format(pkg.name, ' ( {} )'.format(pkg.version) if pkg.version else '')
self.show_message(title=self.i18n['action.update.install_req.fail.title'],
body=self.i18n['action.update.install_req.fail.body'].format(label),
type_=MessageType.ERROR)
return False
return True
def _sort_packages(self, pkgs: List[SoftwarePackage], app_config: dict) -> Tuple[bool, List[SoftwarePackage]]:
if bool(app_config['updates']['sort_packages']):
self.change_substatus(self.i18n['action.update.status.sorting'])
sorted_pkgs = self.manager.sort_update_order([view.model for view in self.pkgs])
else:
sorted_pkgs = pkgs
if len(sorted_pkgs) > 1:
opts = [self._pkg_as_option(p, tooltip=False) for p in sorted_pkgs]
proceed = self.request_confirmation(title=self.i18n['action.update.order.title'],
body=self.i18n['action.update.order.body'] + ':',
components=[MultipleSelectComponent(label='', options=opts, default_options=set(opts))],
confirmation_label=self.i18n['continue'].capitalize(),
deny_label=self.i18n['cancel'].capitalize())
else:
proceed = True
return proceed, sorted_pkgs
def run(self): def run(self):
success = False success = False
@@ -108,16 +172,16 @@ class UpdateSelectedApps(AsyncAction):
app_config = config.read_config() app_config = config.read_config()
models = [view.model for view in self.pkgs] models = [view.model for view in self.pkgs]
required_pkgs = self.manager.get_update_requirements(models, self)
# TODO stopped here: show required packages
if bool(app_config['updates']['sort_packages']): if not self._handle_update_requirements(models):
self.change_substatus(self.i18n['action.update.status.sorting']) return
sorted_pkgs = self.manager.sort_update_order()
else:
sorted_pkgs = models
# TODO show sort order proceed, sorted_pkgs = self._sort_packages(models, app_config)
if not proceed:
self.notify_finished({'success': success, 'updated': updated, 'types': updated_types})
self.pkgs = None
return
for pkg in sorted_pkgs: for pkg in sorted_pkgs:
self.change_substatus('') self.change_substatus('')

View File

@@ -268,4 +268,11 @@ interval=interval
installation=instal·lació installation=instal·lació
download=download download=download
clean=netejar clean=netejar
action.update.status.sorting=Determinant el millor ordre dactualització action.update.status.sorting=Determinant el millor ordre dactualització
action.update.requirements.title=Requisits dactualització
action.update.requirements.body=Abans dactualitzar cal instal·lar les següents aplicacions / paquets
action.update.install_req.fail.title=Ha fallat la instal·lació
action.update.install_req.fail.body=No s'ha pogut instal·lar {}
action.update.requirements.status=Verificant els requisits
action.update.order.title=Ordre d'actualització
action.update.order.body=L'actualització es realitzarà en l'ordre següent

View File

@@ -223,4 +223,11 @@ interval=intervall
installation=Installation installation=Installation
download=download download=download
clean=reinigen clean=reinigen
action.update.status.sorting=Determining the best update order action.update.status.sorting=Determining the best update order
action.update.requirements.title=Upgrade requirements
action.update.requirements.body=The following applications / packages must be installed before upgrading
action.update.install_req.fail.title=Installation failed
action.update.install_req.fail.body=It was not possible to install {}
action.update.requirements.status=Checking requirements
action.update.order.title=Upgrade order
action.update.order.body=The upgrade will be performed in the following order

View File

@@ -230,4 +230,11 @@ interval=interval
installation=installation installation=installation
download=download download=download
clean=clean clean=clean
action.update.status.sorting=Determining the best update order action.update.status.sorting=Determining the best update order
action.update.requirements.title=Upgrade requirements
action.update.requirements.body=The following applications / packages must be installed before upgrading
action.update.install_req.fail.title=Installation failed
action.update.install_req.fail.body=It was not possible to install {}
action.update.requirements.status=Checking requirements
action.update.order.title=Upgrade order
action.update.order.body=The upgrade will be performed in the following order

View File

@@ -271,4 +271,11 @@ interval=intervalo
installation=instalación installation=instalación
download=descarga download=descarga
clean=limpiar clean=limpiar
action.update.status.sorting=Determinando el mejor orden de actualización action.update.status.sorting=Determinando el mejor orden de actualización
action.update.requirements.title=Requisitos de actualización
action.update.requirements.body=Las siguientes aplicaciones / paquetes deben estar instalados antes de actualizar
action.update.install_req.fail.title=Falló la instalación
action.update.install_req.fail.body=No fue posible instalar {}
action.update.requirements.status=Verificando los requisitos
action.update.order.title=Orden de actualización
action.update.order.body=La actualización se realizará en el siguiente orden

View File

@@ -223,4 +223,11 @@ interval=intervallo
installation=installazione installation=installazione
download=download download=download
clean=pulire clean=pulire
action.update.status.sorting=Sto determinando il miglior ordine di aggiornamento action.update.status.sorting=Sto determinando il miglior ordine di aggiornamento
action.update.requirements.title=Requisiti di aggiornamento
action.update.requirements.body=Le seguenti applicazioni / pacchetti devono essere installati prima dell'aggiornamento
action.update.install_req.fail.title=Installazione non riuscita
action.update.install_req.fail.body=Non è stato possibile installare {}
action.update.requirements.status=Verificando i requisiti
action.update.order.title=Ordine di aggiornamento
action.update.order.body=L'aggiornamento verrà eseguito nel seguente ordine

View File

@@ -274,4 +274,11 @@ interval=intervalo
installation=instalação installation=instalação
download=download download=download
clean=limpar clean=limpar
action.update.status.sorting=Determinando a melhor ordem de atualização action.update.status.sorting=Determinando a melhor ordem de atualização
action.update.requirements.title=Requisitos de atualização
action.update.requirements.body=Os seguintes aplicativos / pacotes precisam ser instalados antes de atualizar
action.update.install_req.fail.title=Instalação falhou
action.update.install_req.fail.body=Não foi possível instalar {}
action.update.requirements.status=Verificando requisitos
action.update.order.title=Ordem de atualização
action.update.order.body=A atualização será realizada na seguinte ordem