diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index a7a462f6..13bef3c7 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -59,7 +59,7 @@ class ArchManager(SoftwareManager): self.aur_cache = context.cache_factory.new() # 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.aur_client = AURClient(context.http_client, context.logger) self.dcache_updater = ArchDiskCacheUpdater(context.logger, context.disk_cache) @@ -157,7 +157,7 @@ class ArchManager(SoftwareManager): for name, data in not_signed.items(): pkg = ArchPackage(name=name, version=data.get('version'), 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.downgrade_enabled = downgrade_enabled @@ -171,7 +171,7 @@ class ArchManager(SoftwareManager): def _fill_mirror_pkgs(self, mirrors: dict, apps: list): # TODO 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.mirror = '' # TODO app.update = False # TODO @@ -304,7 +304,7 @@ class ArchManager(SoftwareManager): def get_info(self, pkg: ArchPackage) -> dict: 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() info = pacman.get_info_dict(pkg.name) @@ -407,7 +407,7 @@ class ArchManager(SoftwareManager): 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) 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: return dep[0] @@ -737,6 +737,8 @@ class ArchManager(SoftwareManager): 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: + self._optimize_makepkg(watcher=handler.watcher) + app_build_dir = '{}/build_{}'.format(BUILD_DIR, int(time.time())) try: @@ -831,9 +833,11 @@ class ArchManager(SoftwareManager): handler = ProcessHandler(watcher) 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 os.path.exists(pkg.get_disk_data_path()): @@ -1017,7 +1021,7 @@ class ArchManager(SoftwareManager): threads = [] for pkg in pkgs: - t = Thread(target=_add_info, args=(pkg, )) + t = Thread(target=_add_info, args=(pkg, ), daemon=True) t.start() threads.append(t) @@ -1082,38 +1086,45 @@ class ArchManager(SoftwareManager): 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]: 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: - pkg_names = {p.name for p in pkgs} - return [ArchPackage(name=pkg[0]) for pkg in deps if pkg[0] not in pkg_names] + map_threads, sorted_pkgs = [], {} + + 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: 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)) - - - diff --git a/bauh/gems/arch/mapper.py b/bauh/gems/arch/mapper.py index 55e05d16..3993d9a3 100644 --- a/bauh/gems/arch/mapper.py +++ b/bauh/gems/arch/mapper.py @@ -4,6 +4,7 @@ from datetime import datetime from bauh.api.abstract.model import PackageStatus from bauh.api.http import HttpClient from bauh.gems.arch.model import ArchPackage +from bauh.view.util.translation import I18n URL_PKG_DOWNLOAD = 'https://aur.archlinux.org/{}' 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: - def __init__(self, http_client: HttpClient): + def __init__(self, http_client: HttpClient, i18n: I18n): self.http_client = http_client + self.i18n = i18n 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: 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 if categories: diff --git a/bauh/gems/arch/model.py b/bauh/gems/arch/model.py index 4b84912d..437b34b3 100644 --- a/bauh/gems/arch/model.py +++ b/bauh/gems/arch/model.py @@ -4,6 +4,7 @@ from typing import List, Set from bauh.api.abstract.model import SoftwarePackage from bauh.commons import resource 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'} @@ -14,7 +15,8 @@ class ArchPackage(SoftwarePackage): package_base: str = None, votes: int = None, popularity: float = None, first_submitted: datetime.datetime = None, last_modified: datetime.datetime = 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) self.package_base = package_base @@ -32,6 +34,7 @@ class ArchPackage(SoftwarePackage): self.desktop_entry = desktop_entry self.src_info = srcinfo self.dependencies = dependencies + self.i18n = i18n @staticmethod def disk_cache_path(pkgname: str, mirror: str): @@ -63,7 +66,7 @@ class ArchPackage(SoftwarePackage): return self.icon_path 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): return self.can_be_run() @@ -121,6 +124,9 @@ class ArchPackage(SoftwarePackage): def has_screenshots(self) -> bool: return False + def get_name_tooltip(self) -> str: + return '{}: {}'.format(self.i18n['repository'], self.mirror) + def __str__(self): return self.__repr__() diff --git a/bauh/gems/arch/pacman.py b/bauh/gems/arch/pacman.py index a99c7569..9651e546 100644 --- a/bauh/gems/arch/pacman.py +++ b/bauh/gems/arch/pacman.py @@ -359,3 +359,10 @@ def read_dependencies(name: str) -> Set[str]: def sync_databases(root_password: str, force: bool = False) -> SimpleProcess: return SimpleProcess(cmd=['pacman', '-Sy{}'.format('y' if force else '')], 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() diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py index 4426a54a..c35d4273 100644 --- a/bauh/view/qt/thread.py +++ b/bauh/view/qt/thread.py @@ -1,3 +1,4 @@ +import os import re import time 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.handler import ProcessWatcher 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.view.core import config from bauh.view.qt import commons @@ -98,6 +99,69 @@ class UpdateSelectedApps(AsyncAction): self.root_password = None 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): success = False @@ -108,16 +172,16 @@ class UpdateSelectedApps(AsyncAction): app_config = config.read_config() 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']): - self.change_substatus(self.i18n['action.update.status.sorting']) - sorted_pkgs = self.manager.sort_update_order() - else: - sorted_pkgs = models + if not self._handle_update_requirements(models): + return - # 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: self.change_substatus('') diff --git a/bauh/view/resources/locale/ca b/bauh/view/resources/locale/ca index e5720ee3..dec9990f 100644 --- a/bauh/view/resources/locale/ca +++ b/bauh/view/resources/locale/ca @@ -268,4 +268,11 @@ interval=interval installation=instal·lació download=download clean=netejar -action.update.status.sorting=Determinant el millor ordre d’actualització \ No newline at end of file +action.update.status.sorting=Determinant el millor ordre d’actualització +action.update.requirements.title=Requisits d’actualització +action.update.requirements.body=Abans d’actualitzar 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 \ No newline at end of file diff --git a/bauh/view/resources/locale/de b/bauh/view/resources/locale/de index 455a83b4..566a1d38 100644 --- a/bauh/view/resources/locale/de +++ b/bauh/view/resources/locale/de @@ -223,4 +223,11 @@ interval=intervall installation=Installation download=download clean=reinigen -action.update.status.sorting=Determining the best update order \ No newline at end of file +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 \ No newline at end of file diff --git a/bauh/view/resources/locale/en b/bauh/view/resources/locale/en index b4cb87f0..cf831bbe 100644 --- a/bauh/view/resources/locale/en +++ b/bauh/view/resources/locale/en @@ -230,4 +230,11 @@ interval=interval installation=installation download=download clean=clean -action.update.status.sorting=Determining the best update order \ No newline at end of file +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 \ No newline at end of file diff --git a/bauh/view/resources/locale/es b/bauh/view/resources/locale/es index a8280fb2..374dd3e9 100644 --- a/bauh/view/resources/locale/es +++ b/bauh/view/resources/locale/es @@ -271,4 +271,11 @@ interval=intervalo installation=instalación download=descarga clean=limpiar -action.update.status.sorting=Determinando el mejor orden de actualización \ No newline at end of file +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 \ No newline at end of file diff --git a/bauh/view/resources/locale/it b/bauh/view/resources/locale/it index 5a8e619c..775d2163 100644 --- a/bauh/view/resources/locale/it +++ b/bauh/view/resources/locale/it @@ -223,4 +223,11 @@ interval=intervallo installation=installazione download=download clean=pulire -action.update.status.sorting=Sto determinando il miglior ordine di aggiornamento \ No newline at end of file +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 \ No newline at end of file diff --git a/bauh/view/resources/locale/pt b/bauh/view/resources/locale/pt index f815c23f..afd3ae7f 100644 --- a/bauh/view/resources/locale/pt +++ b/bauh/view/resources/locale/pt @@ -274,4 +274,11 @@ interval=intervalo installation=instalação download=download clean=limpar -action.update.status.sorting=Determinando a melhor ordem de atualização \ No newline at end of file +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 \ No newline at end of file