diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b61a998f..6cefc2b59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,45 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [0.9.24] 2021-12-17 +### Features +- Web + - new custom action button to install apps (to improve usability since some users don't know about how to install Web apps through the search bar) +

+ +

+ - using the new Web environment specifications downloaded from [bauh-files](https://github.com/vinifmor/bauh-files/blob/master/web/env/v2/environment.yml) + +### Improvements +- General + - handling http redirect errors + - memory usage improvements when retrieving available custom actions + - code refactoring + +- AppImage + - not enabled for non-x86_64 systems + +- Web + - using custom installation properties by Electron version if required (available on **bauh-files**). e.g: custom User-Agent for WhatsApp Web on Electron 13.6.X. + - checking for javascript fixes based on the Electron version (saved on disk as `~/.local/share/bauh/web/fixes/electron_{branch}/{app_name}.js`) + - handling http redirect errors + - installation form title + - default pre-selected installation category is now "Network" (Internet) + +- UI + - only displaying a confirmation dialog for custom actions that start immediately + - not depending on system's confirmation dialog icon + +### Fixes +- Web + - wrong spelling (i18n) + +- UI + - crashing when resizing with floats instead of integers [#216](https://github.com/vinifmor/bauh/issues/216) + - crashing when using floats for spinner components [#217](https://github.com/vinifmor/bauh/issues/217) + - crashing for custom actions that can request a system backup (e.g: Arch -> Quick system upgrade) + - initialization panel's lower components positioning + ## [0.9.23] 2021-12-10 ### Features - General diff --git a/README.md b/README.md index 4eeb4b293..fd8322fa1 100644 --- a/README.md +++ b/README.md @@ -324,8 +324,8 @@ will be generated at `~/.local/share/bauh/web/env` (or `/usr/local/share/bauh/we - It supports DRM protected content through a custom Electron implementation provided by [castLabs](https://github.com/castlabs/electron-releases). nativefier handles the switch between the official Electron and the custom. - The isolated environment is created based on the settings defined in [environment.yml](https://raw.githubusercontent.com/vinifmor/bauh-files/master/web/env/v1/environment.yml) (downloaded during runtime). -- Some applications require Javascript fixes to properly work. If there is a known fix, bauh will download the file from [fix](https://github.com/vinifmor/bauh-files/tree/master/web/fix) and -attach it to the generated app. +- Some applications require Javascript fixes to properly work. If there is a known fix, bauh will download the file from [fix](https://github.com/vinifmor/bauh-files/tree/master/web/env/v2/fix) and +attach it to the generated app. The fix files are saved on the disk following the pattern `~/.local/share/bauh/web/fixes/electron_{branch}/{app_name}.js` (or `/usr/local/share/bauh/web/fixes/...` for **root**) - The installed applications are located at `~/.local/share/bauh/installed` (or `/usr/local/share/bauh/web/installed` for **root**). - A desktop entry / menu shortcut will be generated for the installed applications at `~/.local/share/applications` (or `/usr/share/applications` for **root**) - If the Tray Mode **Start Minimized** is defined during the installation setup, a desktop entry will be also generated at `~/.config/autostart` (or `/etc/xdg/autostart` for **root**) diff --git a/bauh/__init__.py b/bauh/__init__.py index 606a7fd63..645edec0b 100644 --- a/bauh/__init__.py +++ b/bauh/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.9.23' +__version__ = '0.9.24' __app_name__ = 'bauh' import os diff --git a/bauh/api/abstract/controller.py b/bauh/api/abstract/controller.py index d270340c6..31dfeab6c 100644 --- a/bauh/api/abstract/controller.py +++ b/bauh/api/abstract/controller.py @@ -4,7 +4,7 @@ from abc import ABC, abstractmethod from enum import Enum from pathlib import Path -from typing import List, Set, Type, Tuple, Optional +from typing import List, Set, Type, Tuple, Optional, Generator import yaml @@ -382,11 +382,11 @@ def save_settings(self, component: ViewComponent) -> Tuple[bool, Optional[List[s """ pass - def get_custom_actions(self) -> List[CustomSoftwareAction]: + def gen_custom_actions(self) -> Generator[CustomSoftwareAction, None, None]: """ - :return: custom actions + :return: generates available custom actions """ - pass + yield from () def fill_sizes(self, pkgs: List[SoftwarePackage]): pass diff --git a/bauh/api/abstract/model.py b/bauh/api/abstract/model.py index e674cf327..c9e8cce6a 100644 --- a/bauh/api/abstract/model.py +++ b/bauh/api/abstract/model.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from enum import Enum -from typing import List, Optional +from typing import List, Optional, Iterable from bauh.api.paths import CACHE_DIR @@ -11,7 +11,8 @@ def __init__(self, i18n_label_key: str, i18n_status_key: str, icon_path: str, ma requires_root: bool, manager: "SoftwareManager" = None, backup: bool = False, refresh: bool = True, i18n_confirm_key: str = None, - requires_internet: bool = False): + requires_internet: bool = False, + requires_confirmation: bool = True): """ :param i18n_label_key: the i18n key that will be used to display the action name :param i18n_status_key: the i18n key that will be used to display the action name being executed @@ -23,6 +24,7 @@ def __init__(self, i18n_label_key: str, i18n_status_key: str, icon_path: str, ma :param refresh: if the a full app refresh should be done if the action succeeds :param i18n_confirm_key: action confirmation message :param requires_internet: if the action requires internet connection to be executed + :param requires_confirmation: if a confirmation popup should be displayed to the user before calling the action """ self.i18n_label_key = i18n_label_key self.i18n_status_key = i18n_status_key @@ -34,6 +36,7 @@ def __init__(self, i18n_label_key: str, i18n_status_key: str, icon_path: str, ma self.refresh = refresh self.i18n_confirm_key = i18n_confirm_key self.requires_internet = requires_internet + self.requires_confirmation = requires_confirmation def __hash__(self): return self.i18n_label_key.__hash__() + self.i18n_status_key.__hash__() + self.manager_method.__hash__() @@ -199,7 +202,7 @@ def get_publisher(self) -> str: """ pass - def get_custom_supported_actions(self) -> List[CustomSoftwareAction]: + def get_custom_actions(self) -> Optional[Iterable[CustomSoftwareAction]]: """ :return: custom supported actions """ diff --git a/bauh/api/abstract/view.py b/bauh/api/abstract/view.py index 351026016..8a0263f3c 100644 --- a/bauh/api/abstract/view.py +++ b/bauh/api/abstract/view.py @@ -289,8 +289,8 @@ def get_tab(self, id_: str) -> TabComponent: class RangeInputComponent(InputViewComponent): - def __init__(self, id_: str, label: str, tooltip: str, min_value: float, max_value: float, - step_value: float, value: float = None, max_width: int = None): + def __init__(self, id_: str, label: str, tooltip: str, min_value: int, max_value: int, + step_value: int, value: Optional[int] = None, max_width: int = None): super(RangeInputComponent, self).__init__(id_=id_) self.label = label self.tooltip = tooltip diff --git a/bauh/api/http.py b/bauh/api/http.py index 20b9a84d8..e4836ec5d 100644 --- a/bauh/api/http.py +++ b/bauh/api/http.py @@ -52,7 +52,13 @@ def get(self, url: str, params: dict = None, headers: dict = None, allow_redirec except Exception as e: if isinstance(e, requests.exceptions.ConnectionError): self.logger.error('Internet seems to be off') - raise + raise e + elif isinstance(e, requests.exceptions.TooManyRedirects): + self.logger.warning(f"Too many redirects for GET -> {url}") + raise e + elif e.__class__ in (requests.exceptions.MissingSchema, requests.exceptions.InvalidSchema): + self.logger.warning(f"The URL '{url}' has an invalid schema") + raise e self.logger.error("Could not retrieve data from '{}'".format(url)) traceback.print_exc() @@ -100,9 +106,14 @@ def get_content_length(self, url: str, session: bool = True) -> Optional[str]: def exists(self, url: str, session: bool = True, timeout: int = 5) -> bool: params = {'url': url, 'allow_redirects': True, 'verify': False, 'timeout': timeout} - if session: - res = self.session.head(**params) - else: - res = self.session.get(**params) + + try: + if session: + res = self.session.head(**params) + else: + res = self.session.get(**params) + except requests.exceptions.TooManyRedirects: + self.logger.warning(f"{url} seems to exist, but too many redirects have happened") + return True return res.status_code in (200, 403) diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py index 2769ba305..6173b8281 100644 --- a/bauh/gems/appimage/controller.py +++ b/bauh/gems/appimage/controller.py @@ -9,7 +9,7 @@ from datetime import datetime from math import floor from pathlib import Path -from typing import Set, Type, List, Tuple, Optional +from typing import Set, Type, List, Tuple, Optional, Iterable, Generator from colorama import Fore from packaging.version import parse as parse_version @@ -27,7 +27,7 @@ from bauh.commons import resource from bauh.commons.boot import CreateConfigFile from bauh.commons.html import bold -from bauh.commons.system import SystemProcess, new_subprocess, ProcessHandler, run_cmd, SimpleProcess +from bauh.commons.system import SystemProcess, new_subprocess, ProcessHandler, SimpleProcess from bauh.gems.appimage import query, INSTALLATION_DIR, APPIMAGE_SHARED_DIR, ROOT_DIR, \ APPIMAGE_CONFIG_DIR, UPDATES_IGNORED_FILE, util, get_default_manual_installation_file_dir, DATABASE_APPS_FILE, \ DATABASE_RELEASES_FILE, APPIMAGE_CACHE_DIR, get_icon_path, DOWNLOAD_DIR @@ -78,24 +78,13 @@ def __init__(self, context: ApplicationContext): self.logger = context.logger self.file_downloader = context.file_downloader self.configman = AppImageConfigManager() - self.custom_actions = [CustomSoftwareAction(i18n_label_key='appimage.custom_action.install_file', - i18n_status_key='appimage.custom_action.install_file.status', - manager=self, - manager_method='install_file', - icon_path=resource.get_path('img/appimage.svg', ROOT_DIR), - requires_root=False), - CustomSoftwareAction(i18n_label_key='appimage.custom_action.update_db', - i18n_status_key='appimage.custom_action.update_db.status', - manager=self, - manager_method='update_database', - icon_path=resource.get_path('img/appimage.svg', ROOT_DIR), - requires_root=False, - requires_internet=True)] - self.custom_app_actions = [CustomSoftwareAction(i18n_label_key='appimage.custom_action.manual_update', + self._custom_actions: Optional[Iterable[CustomSoftwareAction]] = None + self.custom_app_actions = (CustomSoftwareAction(i18n_label_key='appimage.custom_action.manual_update', i18n_status_key='appimage.custom_action.manual_update.status', manager_method='update_file', requires_root=False, - icon_path=resource.get_path('img/upgrade.svg', ROOT_DIR))] + icon_path=resource.get_path('img/upgrade.svg', ROOT_DIR), + requires_confirmation=False),) def install_file(self, root_password: str, watcher: ProcessWatcher) -> bool: file_chooser = FileChooserComponent(label=self.i18n['file'].capitalize(), @@ -692,6 +681,9 @@ def _is_sqlite3_available(self) -> bool: return bool(shutil.which('sqlite3')) def can_work(self) -> Tuple[bool, Optional[str]]: + if not self.context.is_system_x86_64(): + return False, self.i18n['message.requires_architecture'].format(arch=bold('x86_64')) + if not self._is_sqlite3_available(): return False, self.i18n['missing_dep'].format(dep=bold('sqlite3')) @@ -859,8 +851,23 @@ def save_settings(self, component: PanelComponent) -> Tuple[bool, Optional[List[ except: return False, [traceback.format_exc()] - def get_custom_actions(self) -> List[CustomSoftwareAction]: - return self.custom_actions + def gen_custom_actions(self) -> Generator[CustomSoftwareAction, None, None]: + if self._custom_actions is None: + self._custom_actions = (CustomSoftwareAction(i18n_label_key='appimage.custom_action.install_file', + i18n_status_key='appimage.custom_action.install_file.status', + manager=self, + manager_method='install_file', + icon_path=resource.get_path('img/appimage.svg', ROOT_DIR), + requires_root=False, + requires_confirmation=False), + CustomSoftwareAction(i18n_label_key='appimage.custom_action.update_db', + i18n_status_key='appimage.custom_action.update_db.status', + manager=self, + manager_method='update_database', + icon_path=resource.get_path('img/appimage.svg', ROOT_DIR), + requires_root=False, + requires_internet=True)) + yield from self._custom_actions def get_upgrade_requirements(self, pkgs: List[AppImage], root_password: str, watcher: ProcessWatcher) -> UpgradeRequirements: to_update = [] diff --git a/bauh/gems/appimage/model.py b/bauh/gems/appimage/model.py index 9da6041ae..985f6176f 100644 --- a/bauh/gems/appimage/model.py +++ b/bauh/gems/appimage/model.py @@ -1,6 +1,6 @@ import re from io import StringIO -from typing import List, Optional +from typing import List, Optional, Iterable from bauh.api.abstract.model import SoftwarePackage, CustomSoftwareAction from bauh.commons import resource @@ -19,7 +19,7 @@ def __init__(self, name: str = None, description: str = None, github: str = None url_download: str = None, url_icon: str = None, url_screenshot: str = None, license: str = None, author: str = None, categories=None, icon_path: str = None, installed: bool = False, url_download_latest_version: str = None, local_file_path: str = None, imported: bool = False, - i18n: I18n = None, install_dir: str = None, custom_actions: List[CustomSoftwareAction] = None, updates_ignored: bool = False, + i18n: I18n = None, install_dir: str = None, custom_actions: Optional[Iterable[CustomSoftwareAction]] = None, updates_ignored: bool = False, symlink: str = None, **kwargs): super(AppImage, self).__init__(id=name, name=name, version=version, latest_version=version, icon_url=url_icon, license=license, description=description, @@ -108,7 +108,7 @@ def get_name_tooltip(self) -> str: return self.name - def get_custom_supported_actions(self) -> List[CustomSoftwareAction]: + def get_custom_actions(self) -> Optional[Iterable[CustomSoftwareAction]]: if self.imported: return self.custom_actions diff --git a/bauh/gems/appimage/resources/locale/ca b/bauh/gems/appimage/resources/locale/ca index 3007e66db..e35c22711 100644 --- a/bauh/gems/appimage/resources/locale/ca +++ b/bauh/gems/appimage/resources/locale/ca @@ -3,12 +3,12 @@ appimage.config.database.expiration.tip=It defines the period (in minutes) in wh appimage.config.suggestions.expiration=Suggestions expiration appimage.config.suggestions.expiration.tip=It defines the period (in hours) in which the suggestions stored in disc will be considered up to date. Use 0 if you always want to update them. appimage.custom_action.install_file=Install AppImage file -appimage.custom_action.install_file.details=Installation details +appimage.custom_action.install_file.details=AppImage installation details appimage.custom_action.install_file.invalid_file=You must define a valid AppImage file appimage.custom_action.install_file.invalid_name=You must define a name for the application appimage.custom_action.install_file.status=Installing AppImage file appimage.custom_action.manual_update=Upgrade file -appimage.custom_action.manual_update.details=Upgrade details +appimage.custom_action.manual_update.details=AppImage upgrade details appimage.custom_action.manual_update.status=Upgrading appimage.custom_action.update_db=Update database appimage.custom_action.update_db.status=Updating database diff --git a/bauh/gems/appimage/resources/locale/de b/bauh/gems/appimage/resources/locale/de index 37780d0ce..7efb48122 100644 --- a/bauh/gems/appimage/resources/locale/de +++ b/bauh/gems/appimage/resources/locale/de @@ -3,12 +3,12 @@ appimage.config.database.expiration.tip=It defines the period (in minutes) in wh appimage.config.suggestions.expiration=Suggestions expiration appimage.config.suggestions.expiration.tip=It defines the period (in hours) in which the suggestions stored in disc will be considered up to date. Use 0 if you always want to update them. appimage.custom_action.install_file=Install AppImage file -appimage.custom_action.install_file.details=Installation details +appimage.custom_action.install_file.details=AppImage installation details appimage.custom_action.install_file.invalid_file=You must define a valid AppImage file appimage.custom_action.install_file.invalid_name=You must define a name for the application appimage.custom_action.install_file.status=Installing AppImage file appimage.custom_action.manual_update=Upgrade file -appimage.custom_action.manual_update.details=Upgrade details +appimage.custom_action.manual_update.details=AppImage upgrade details appimage.custom_action.manual_update.status=Upgrading appimage.custom_action.update_db=Update database appimage.custom_action.update_db.status=Updating database diff --git a/bauh/gems/appimage/resources/locale/en b/bauh/gems/appimage/resources/locale/en index 6e24001a8..841bbef01 100644 --- a/bauh/gems/appimage/resources/locale/en +++ b/bauh/gems/appimage/resources/locale/en @@ -3,12 +3,12 @@ appimage.config.database.expiration.tip=It defines the period (in minutes) in wh appimage.config.suggestions.expiration=Suggestions expiration appimage.config.suggestions.expiration.tip=It defines the period (in hours) in which the suggestions stored in disc will be considered up to date. Use 0 if you always want to update them. appimage.custom_action.install_file=Install AppImage file -appimage.custom_action.install_file.details=Installation details +appimage.custom_action.install_file.details=AppImage installation details appimage.custom_action.install_file.invalid_file=You must define a valid AppImage file appimage.custom_action.install_file.invalid_name=You must define a name for the application appimage.custom_action.install_file.status=Installing AppImage file appimage.custom_action.manual_update=Upgrade file -appimage.custom_action.manual_update.details=Upgrade details +appimage.custom_action.manual_update.details=AppImage upgrade details appimage.custom_action.manual_update.status=Upgrading appimage.custom_action.update_db=Update database appimage.custom_action.update_db.status=Updating database diff --git a/bauh/gems/appimage/resources/locale/es b/bauh/gems/appimage/resources/locale/es index f35d6d298..605091792 100644 --- a/bauh/gems/appimage/resources/locale/es +++ b/bauh/gems/appimage/resources/locale/es @@ -3,12 +3,12 @@ appimage.config.database.expiration.tip=Define el período (en minutos) en el qu appimage.config.suggestions.expiration=Expiración de sugerencias appimage.config.suggestions.expiration.tip=Define el período (en horas) en el que la sugerencias almacenadas en disco seran consideradas actualizadas. Use 0 si desea siempre actualizarlas. appimage.custom_action.install_file=Instalar archivo AppImage -appimage.custom_action.install_file.details=Detalles de instalación +appimage.custom_action.install_file.details=Detalles de instalación de AppImage appimage.custom_action.install_file.invalid_file=Debe definir un archivo AppImage válido appimage.custom_action.install_file.invalid_name=Debe definir un nombre para la aplicación appimage.custom_action.install_file.status=Instalando archivo AppImage appimage.custom_action.manual_update=Actualizar archivo -appimage.custom_action.manual_update.details=Detalles de actualización +appimage.custom_action.manual_update.details=Detalles de actualización de AppImage appimage.custom_action.manual_update.status=Actualizando appimage.custom_action.update_db=Actualizar base de datos appimage.custom_action.update_db.status=Actualizando base de datos diff --git a/bauh/gems/appimage/resources/locale/fr b/bauh/gems/appimage/resources/locale/fr index bb71fab32..10dea654c 100644 --- a/bauh/gems/appimage/resources/locale/fr +++ b/bauh/gems/appimage/resources/locale/fr @@ -2,7 +2,7 @@ appimage.config.database.expiration=Database expiration appimage.config.database.expiration.tip=It defines the period (in minutes) in which the database will be considered up to date during the initialization process. Use 0 if you always want to update it. appimage.config.suggestions.expiration=Suggestions expiration appimage.config.suggestions.expiration.tip=It defines the period (in hours) in which the suggestions stored in disc will be considered up to date. Use 0 if you always want to update them.appimage.custom_action.install_file=Installer fichier AppImage -appimage.custom_action.install_file.details=Détails d'installation +appimage.custom_action.install_file.details=Détails d'installation d'AppImage appimage.custom_action.install_file.invalid_file=Vous devez définir un fichier AppImage valide appimage.custom_action.install_file.invalid_name=Vous devez choisir un nom à l'application appimage.custom_action.install_file.status=Installation du fichier AppImage diff --git a/bauh/gems/appimage/resources/locale/it b/bauh/gems/appimage/resources/locale/it index c7f3214a6..2deb5b5e8 100644 --- a/bauh/gems/appimage/resources/locale/it +++ b/bauh/gems/appimage/resources/locale/it @@ -3,12 +3,12 @@ appimage.config.database.expiration.tip=It defines the period (in minutes) in wh appimage.config.suggestions.expiration=Suggestions expiration appimage.config.suggestions.expiration.tip=It defines the period (in hours) in which the suggestions stored in disc will be considered up to date. Use 0 if you always want to update them. appimage.custom_action.install_file=Install AppImage file -appimage.custom_action.install_file.details=Installation details +appimage.custom_action.install_file.details=AppImage installation details appimage.custom_action.install_file.invalid_file=You must define a valid AppImage file appimage.custom_action.install_file.invalid_name=You must define a name for the application appimage.custom_action.install_file.status=Installing AppImage file appimage.custom_action.manual_update=Upgrade file -appimage.custom_action.manual_update.details=Upgrade details +appimage.custom_action.manual_update.details=AppImage upgrade details appimage.custom_action.manual_update.status=Upgrading appimage.custom_action.update_db=Update database appimage.custom_action.update_db.status=Updating database diff --git a/bauh/gems/appimage/resources/locale/pt b/bauh/gems/appimage/resources/locale/pt index 34cdfd5c4..791c204a7 100644 --- a/bauh/gems/appimage/resources/locale/pt +++ b/bauh/gems/appimage/resources/locale/pt @@ -3,12 +3,12 @@ appimage.config.database.expiration.tip=Define o período (em minutos) no qual o appimage.config.suggestions.expiration=Expiração das sugestões appimage.config.suggestions.expiration.tip=Define o período (em horas) no qual as sugestões armazenadas em disco serão consideradas atualizadas. Use 0 se quiser que elas sempre seja atualizadas. appimage.custom_action.install_file=Instalar arquivo AppImage -appimage.custom_action.install_file.details=Detalhes de instalação +appimage.custom_action.install_file.details=Detalhes de instalação de AppImage appimage.custom_action.install_file.invalid_file=Você precisa definir um arquivo AppImage válido appimage.custom_action.install_file.invalid_name=Você precisa definir um nome para o aplicativo appimage.custom_action.install_file.status=Instalando arquivo AppImage appimage.custom_action.manual_update=Atualizar arquivo -appimage.custom_action.manual_update.details=Detalhes de atualização +appimage.custom_action.manual_update.details=Detalhes de atualização de AppImage appimage.custom_action.manual_update.status=Atualizando appimage.custom_action.update_db=Atualizar banco de dados appimage.custom_action.update_db.status=Atualizando banco de dados diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index 251f4a51a..635ce9497 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -12,7 +12,7 @@ from pathlib import Path from pwd import getpwnam from threading import Thread -from typing import List, Set, Type, Tuple, Dict, Iterable, Optional, Collection +from typing import List, Set, Type, Tuple, Dict, Iterable, Optional, Collection, Generator import requests from dateutil.parser import parse as parse_date @@ -211,41 +211,7 @@ def __init__(self, context: ApplicationContext, disk_cache_updater: Optional[Arc self.categories = {} self.deps_analyser = DependenciesAnalyser(self.aur_client, self.i18n) self.http_client = context.http_client - self.custom_actions = { - 'sys_up': CustomSoftwareAction(i18n_label_key='arch.custom_action.upgrade_system', - i18n_status_key='arch.custom_action.upgrade_system.status', - manager_method='upgrade_system', - icon_path=get_icon_path(), - requires_root=True, - backup=True, - manager=self), - 'ref_dbs': CustomSoftwareAction(i18n_label_key='arch.custom_action.refresh_dbs', - i18n_status_key='arch.sync_databases.substatus', - manager_method='sync_databases', - icon_path=get_icon_path(), - requires_root=True, - manager=self), - 'ref_mirrors': CustomSoftwareAction(i18n_label_key='arch.custom_action.refresh_mirrors', - i18n_status_key='arch.task.mirrors', - manager_method='refresh_mirrors', - icon_path=get_icon_path(), - requires_root=True, - manager=self), - 'clean_cache': CustomSoftwareAction(i18n_label_key='arch.custom_action.clean_cache', - i18n_status_key='arch.custom_action.clean_cache.status', - manager_method='clean_cache', - icon_path=get_icon_path(), - requires_root=True, - refresh=False, - manager=self), - 'setup_snapd': CustomSoftwareAction(i18n_label_key='arch.custom_action.setup_snapd', - i18n_status_key='arch.custom_action.setup_snapd.status', - manager_method='setup_snapd', - icon_path=get_icon_path(), - requires_root=False, - refresh=False, - manager=self), - } + self._custom_actions: Optional[Dict[str, CustomSoftwareAction]] = None self.index_aur = None self.re_file_conflict = re.compile(r'[\w\d\-_.]+:') self.disk_cache_updater = disk_cache_updater @@ -3040,24 +3006,60 @@ def get_upgrade_requirements(self, pkgs: List[ArchPackage], root_password: str, except PackageNotFoundException: pass # when nothing is returned, the upgrade is called off by the UI - def get_custom_actions(self) -> List[CustomSoftwareAction]: - actions = [] + def gen_custom_actions(self) -> Generator[CustomSoftwareAction, None, None]: + if self._custom_actions is None: + self._custom_actions = { + 'sys_up': CustomSoftwareAction(i18n_label_key='arch.custom_action.upgrade_system', + i18n_status_key='arch.custom_action.upgrade_system.status', + manager_method='upgrade_system', + icon_path=get_icon_path(), + requires_root=True, + backup=True, + manager=self), + 'ref_dbs': CustomSoftwareAction(i18n_label_key='arch.custom_action.refresh_dbs', + i18n_status_key='arch.sync_databases.substatus', + manager_method='sync_databases', + icon_path=get_icon_path(), + requires_root=True, + manager=self), + 'ref_mirrors': CustomSoftwareAction(i18n_label_key='arch.custom_action.refresh_mirrors', + i18n_status_key='arch.task.mirrors', + manager_method='refresh_mirrors', + icon_path=get_icon_path(), + requires_root=True, + manager=self, + requires_confirmation=False), + 'clean_cache': CustomSoftwareAction(i18n_label_key='arch.custom_action.clean_cache', + i18n_status_key='arch.custom_action.clean_cache.status', + manager_method='clean_cache', + icon_path=get_icon_path(), + requires_root=True, + refresh=False, + manager=self, + requires_confirmation=False), + 'setup_snapd': CustomSoftwareAction(i18n_label_key='arch.custom_action.setup_snapd', + i18n_status_key='arch.custom_action.setup_snapd.status', + manager_method='setup_snapd', + icon_path=get_icon_path(), + requires_root=False, + refresh=False, + manager=self, + requires_confirmation=False) + } arch_config = self.configman.get_config() if pacman.is_mirrors_available(): - actions.append(self.custom_actions['ref_mirrors']) + yield self._custom_actions['ref_mirrors'] - actions.append(self.custom_actions['ref_dbs']) - actions.append(self.custom_actions['clean_cache']) + yield self._custom_actions['ref_dbs'] + yield self._custom_actions['clean_cache'] if bool(arch_config['repositories']): - actions.append(self.custom_actions['sys_up']) + yield self._custom_actions['sys_up'] if pacman.is_snapd_installed(): - actions.append(self.custom_actions['setup_snapd']) - - return actions + yield self._custom_actions['setup_snapd'] def fill_sizes(self, pkgs: List[ArchPackage]): installed, new, all_names, installed_names = [], [], [], [] diff --git a/bauh/gems/arch/model.py b/bauh/gems/arch/model.py index 6c628e493..c0c248082 100644 --- a/bauh/gems/arch/model.py +++ b/bauh/gems/arch/model.py @@ -1,4 +1,4 @@ -from typing import List, Set, Optional +from typing import List, Set, Optional, Iterable from bauh.api.abstract.model import SoftwarePackage, CustomSoftwareAction from bauh.commons import resource @@ -200,7 +200,7 @@ def __eq__(self, other): def get_cached_pkgbuild_path(self) -> str: return '{}/PKGBUILD'.format(self.get_disk_cache_path()) - def get_custom_supported_actions(self) -> List[CustomSoftwareAction]: + def get_custom_actions(self) -> Optional[Iterable[CustomSoftwareAction]]: if self.installed and self.repository == 'aur': actions = [ACTION_AUR_REINSTALL] diff --git a/bauh/gems/snap/controller.py b/bauh/gems/snap/controller.py index 36e3a2495..eaba70868 100644 --- a/bauh/gems/snap/controller.py +++ b/bauh/gems/snap/controller.py @@ -43,7 +43,7 @@ def __init__(self, context: ApplicationContext): self.suggestions_cache = context.cache_factory.new() self.info_path = None self.configman = SnapConfigManager() - self.custom_actions = [ + self.custom_actions = ( CustomSoftwareAction(i18n_status_key='snap.action.refresh.status', i18n_label_key='snap.action.refresh.label', icon_path=resource.get_path('img/refresh.svg', ROOT_DIR), @@ -55,8 +55,9 @@ def __init__(self, context: ApplicationContext): i18n_confirm_key='snap.action.channel.confirm', icon_path=resource.get_path('img/refresh.svg', ROOT_DIR), manager_method='change_channel', - requires_root=True) - ] + requires_root=True, + requires_confirmation=False) + ) def _fill_categories(self, app: SnapApplication): categories = self.categories.get(app.name.lower()) diff --git a/bauh/gems/snap/model.py b/bauh/gems/snap/model.py index d873b54ea..75a5a73b1 100644 --- a/bauh/gems/snap/model.py +++ b/bauh/gems/snap/model.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Set +from typing import Optional, Set, Iterable from bauh.api.abstract.model import SoftwarePackage, CustomSoftwareAction from bauh.commons import resource @@ -10,7 +10,7 @@ class SnapApplication(SoftwarePackage): def __init__(self, id: str = None, name: str = None, version: str = None, latest_version: str = None, description: str = None, publisher: str = None, rev: str = None, notes: str = None, confinement: str = None, verified_publisher: bool = False, - extra_actions: List[CustomSoftwareAction] = None, + extra_actions: Optional[Iterable[CustomSoftwareAction]] = None, screenshots: Optional[Set[str]] = None, license: Optional[str] = None, installed: bool = False, @@ -92,7 +92,7 @@ def can_be_run(self) -> bool: def get_publisher(self): return self.publisher - def get_custom_supported_actions(self) -> List[CustomSoftwareAction]: + def get_custom_actions(self) -> Optional[Iterable[CustomSoftwareAction]]: if self.installed: return self.extra_actions diff --git a/bauh/gems/web/__init__.py b/bauh/gems/web/__init__.py index 9041a0bdd..dbb27a29b 100644 --- a/bauh/gems/web/__init__.py +++ b/bauh/gems/web/__init__.py @@ -10,7 +10,7 @@ WEB_CACHE_DIR = f'{CACHE_DIR}/web' INSTALLED_PATH = f'{WEB_SHARED_DIR}/installed' ENV_PATH = f'{WEB_SHARED_DIR}/env' -FIXES_PATH = f'{WEB_SHARED_DIR}/fixes' +FIX_FILE_PATH = WEB_SHARED_DIR + '/fixes/{electron_branch}/{app_id}.js' NODE_DIR_PATH = f'{ENV_PATH}/node' NODE_PATHS = {f'{NODE_DIR_PATH}/bin'} NODE_BIN_PATH = f'{NODE_DIR_PATH}/bin/node' @@ -18,15 +18,12 @@ NODE_MODULES_PATH = f'{ENV_PATH}/node_modules' NATIVEFIER_BIN_PATH = f'{NODE_MODULES_PATH}/.bin/nativefier' ELECTRON_CACHE_DIR = f'{ENV_PATH}/electron' -ELECTRON_DOWNLOAD_URL = 'https://github.com/electron/electron/releases/download/v{version}/electron-v{version}-linux-{arch}.zip' -ELECTRON_SHA256_URL = 'https://github.com/electron/electron/releases/download/v{version}/SHASUMS256.txt' -ELECTRON_WIDEVINE_URL = 'https://github.com/castlabs/electron-releases/releases/download/v{version}-wvvmp/electron-v{version}-wvvmp-linux-{arch}.zip' -ELECTRON_WIDEVINE_SHA256_URL = 'https://github.com/castlabs/electron-releases/releases/download/v{version}-wvvmp/SHASUMS256.txt' -URL_ENVIRONMENT_SETTINGS = f'https://raw.githubusercontent.com/vinifmor/{__app_name__}-files/master/web/env/v1/environment.yml' +URL_ENVIRONMENT_SETTINGS = f'https://raw.githubusercontent.com/vinifmor/bauh-files/master/web/env/v2/environment.yml' DESKTOP_ENTRY_PATH_PATTERN = f'{DESKTOP_ENTRIES_DIR}/{__app_name__}.web.' + '{name}.desktop' -URL_FIX_PATTERN = "https://raw.githubusercontent.com/vinifmor/bauh-files/master/web/fix/{url}.js" -URL_SUGGESTIONS = "https://raw.githubusercontent.com/vinifmor/bauh-files/master/web/env/v1/suggestions.yml" -UA_CHROME = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36' +URL_FIX_PATTERN = "https://raw.githubusercontent.com/vinifmor/bauh-files/master/web/env/v2/fix/{domain}/{electron_branch}/fix.js" +URL_PROPS_PATTERN = "https://raw.githubusercontent.com/vinifmor/bauh-files/master/web/env/v2/fix/{domain}/{electron_branch}/properties" +URL_SUGGESTIONS = "https://raw.githubusercontent.com/vinifmor/bauh-files/master/web/env/v2/suggestions.yml" +UA_CHROME = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36' TEMP_PATH = f'{TEMP_DIR}/web' SEARCH_INDEX_FILE = f'{WEB_CACHE_DIR}/index.yml' SUGGESTIONS_CACHE_FILE = f'{WEB_CACHE_DIR}/suggestions.yml' @@ -34,7 +31,6 @@ CONFIG_FILE = f'{CONFIG_DIR}/web.yml' ENVIRONMENT_SETTINGS_CACHED_FILE = f'{WEB_CACHE_DIR}/environment.yml' ENVIRONMENT_SETTINGS_TS_FILE = f'{WEB_CACHE_DIR}/environment.ts' -NATIVEFIER_BASE_URL = 'https://github.com/nativefier/nativefier/archive/v{version}.tar.gz' def get_icon_path() -> str: diff --git a/bauh/gems/web/controller.py b/bauh/gems/web/controller.py index 76972514c..94efed26c 100644 --- a/bauh/gems/web/controller.py +++ b/bauh/gems/web/controller.py @@ -9,12 +9,12 @@ from math import floor from pathlib import Path from threading import Thread -from typing import List, Type, Set, Tuple, Optional +from typing import List, Type, Set, Tuple, Optional, Dict, Generator, Iterable import requests import yaml from colorama import Fore -from requests import exceptions, Response +from requests import Response from bauh.api.abstract.context import ApplicationContext from bauh.api.abstract.controller import SoftwareManager, SearchResult, UpgradeRequirements, TransactionResult, \ @@ -32,8 +32,8 @@ from bauh.commons.html import bold from bauh.commons.system import ProcessHandler, get_dir_size, get_human_size_str, SimpleProcess from bauh.gems.web import INSTALLED_PATH, nativefier, DESKTOP_ENTRY_PATH_PATTERN, URL_FIX_PATTERN, ENV_PATH, UA_CHROME, \ - SUGGESTIONS_CACHE_FILE, ROOT_DIR, TEMP_PATH, FIXES_PATH, ELECTRON_CACHE_DIR, \ - get_icon_path + SUGGESTIONS_CACHE_FILE, ROOT_DIR, TEMP_PATH, FIX_FILE_PATH, ELECTRON_CACHE_DIR, \ + get_icon_path, URL_PROPS_PATTERN from bauh.gems.web.config import WebConfigManager from bauh.gems.web.environment import EnvironmentUpdater, EnvironmentComponent from bauh.gems.web.model import WebApplication @@ -74,14 +74,8 @@ def __init__(self, context: ApplicationContext, suggestions_loader: Optional[Sug self.suggestions = {} self.configman = WebConfigManager() self.idxman = SearchIndexManager(logger=context.logger) - self.custom_actions = [CustomSoftwareAction(i18n_label_key='web.custom_action.clean_env', - i18n_status_key='web.custom_action.clean_env.status', - manager=self, - manager_method='clean_environment', - icon_path=resource.get_path('img/web.svg', ROOT_DIR), - requires_root=False, - refresh=False)] - + self._custom_actions: Optional[Iterable[CustomSoftwareAction]] = None + def _get_lang_header(self) -> str: try: system_locale = locale.getdefaultlocale() @@ -181,8 +175,9 @@ def _get_app_description(self, url: str, soup: "BeautifulSoup") -> str: return description - def _get_fix_for(self, url_no_protocol: str) -> str: - fix_url = URL_FIX_PATTERN.format(url=url_no_protocol) + def _get_fix_for(self, url_domain: str, electron_branch: str) -> str: + fix_url = URL_FIX_PATTERN.format(domain=url_domain, + electron_branch=electron_branch) try: res = self.http_client.get(fix_url, session=False) @@ -191,19 +186,46 @@ def _get_fix_for(self, url_no_protocol: str) -> str: except Exception as e: self.logger.warning("Error when trying to retrieve a fix for {}: {}".format(fix_url, e.__class__.__name__)) + def _get_custom_properties(self, url_domain: str, electron_branch: str) -> Optional[Dict[str, object]]: + props_url = URL_PROPS_PATTERN.format(domain=url_domain, + electron_branch=electron_branch) + + try: + res = self.http_client.get(props_url, session=False) + if res: + props = {} + for line in res.text.split('\n'): + line_strip = line.strip() + if line_strip: + line_split = line_strip.split('=', 1) + + if len(line_split) == 2: + key, val = line_split[0].strip(), line_split[1].strip() + + if key: + props[key] = val + + return props + + except Exception as e: + self.logger.warning(f"Error when trying to retrieve custom installation properties for {props_url}: {e.__class__.__name__}") + + def _map_electron_branch(self, version: str) -> str: + return f"electron_{'_'.join(version.split('.')[0:-1])}_X" + def _strip_url_protocol(self, url: str) -> str: return RE_PROTOCOL_STRIP.split(url)[1].strip().lower() - def serialize_to_disk(self, pkg: SoftwarePackage, icon_bytes: bytes, only_icon: bool): + def serialize_to_disk(self, pkg: SoftwarePackage, icon_bytes: Optional[bytes], only_icon: bool): super(WebApplicationManager, self).serialize_to_disk(pkg=pkg, icon_bytes=None, only_icon=False) - def _request_url(self, url: str) -> Response: + def _request_url(self, url: str) -> Optional[Response]: headers = {'Accept-language': self._get_lang_header(), 'User-Agent': UA_CHROME} try: return self.http_client.get(url, headers=headers, ignore_ssl=True, single_call=True, session=False, allow_redirects=True) - except exceptions.ConnectionError as e: - self.logger.warning("Could not get {}: {}".format(url, e.__class__.__name__)) + except Exception as e: + self.logger.warning(f"Could not GET '{url}'. Exception: {e.__class__.__name__}") def _map_url(self, url: str) -> Tuple["BeautifulSoup", requests.Response]: url_res = self._request_url(url) @@ -402,16 +424,15 @@ def uninstall(self, pkg: WebApplication, root_password: str, watcher: ProcessWat type_=MessageType.WARNING) traceback.print_exc() - self.logger.info("Checking if there is any Javascript fix file associated with {} ".format(pkg.name)) - - fix_path = '{}/{}.js'.format(FIXES_PATH, pkg.id) + self.logger.info(f"Checking for Javascript fix file associated with {pkg.name}") + fix_path = FIX_FILE_PATH.format(app_id=pkg.id, electron_branch=self._map_electron_branch(pkg.version)) if os.path.isfile(fix_path): - self.logger.info("Removing fix file '{}'".format(fix_path)) + self.logger.info(f"Removing fix file '{fix_path}'") try: os.remove(fix_path) except: - self.logger.error("Could not remove fix file '{}'".format(fix_path)) + self.logger.error(f"Could not remove fix file '{fix_path}'") traceback.print_exc() watcher.show_message(title=self.i18n['error'], body=self.i18n['web.uninstall.error.remove'].format(bold(fix_path)), @@ -446,10 +467,11 @@ def get_info(self, pkg: WebApplication) -> dict: def get_history(self, pkg: SoftwarePackage) -> PackageHistory: pass - def _ask_install_options(self, app: WebApplication, watcher: ProcessWatcher) -> Tuple[bool, List[str]]: + def _ask_install_options(self, app: WebApplication, watcher: ProcessWatcher, pre_validated: bool) -> Tuple[bool, List[str]]: watcher.change_substatus(self.i18n['web.install.substatus.options']) - inp_url = TextInputComponent(label=self.i18n['address'], value=app.url, read_only=True) + inp_url = TextInputComponent(label=self.i18n['address'].capitalize() + ' (URL)', capitalize_label=False, value=app.url, + read_only=pre_validated, placeholder=f"({self.i18n['example.short']}: https://myapp123.com)") inp_name = TextInputComponent(label=self.i18n['name'], value=app.name) inp_desc = TextInputComponent(label=self.i18n['description'], value=app.description) @@ -463,6 +485,11 @@ def _ask_install_options(self, app: WebApplication, watcher: ProcessWatcher) -> if opt.value == app.categories[0]: def_cat = opt break + else: + for op in cat_ops: + if op.value == 'Network': + def_cat = op + break inp_cat = SingleSelectComponent(label=self.i18n['category'], type_=SelectViewType.COMBO, options=cat_ops, default_option=def_cat) op_wv = InputOption(id_='widevine', label=self.i18n['web.install.option.widevine.label'] + ' (DRM)', value="--widevine", tooltip=self.i18n['web.install.option.widevine.tip']) @@ -486,11 +513,15 @@ def _ask_install_options(self, app: WebApplication, watcher: ProcessWatcher) -> icon_op_ded = InputOption(id_='icon_ded', label=self.i18n['web.install.option.wicon.deducted.label'], value=0, tooltip=self.i18n['web.install.option.wicon.deducted.tip'].format('Nativefier')) - icon_op_disp = InputOption(id_='icon_disp', label=self.i18n['web.install.option.wicon.displayed.label'], - value=1, tooltip=self.i18n['web.install.option.wicon.displayed.tip']) + + if pre_validated: + icon_op_disp = InputOption(id_='icon_disp', label=self.i18n['web.install.option.wicon.displayed.label'], + value=1, tooltip=self.i18n['web.install.option.wicon.displayed.tip']) + else: + icon_op_disp = None inp_icon = SingleSelectComponent(type_=SelectViewType.COMBO, - options=[icon_op_disp, icon_op_ded], + options=[op for op in (icon_op_ded, icon_op_disp) if op], default_option=icon_op_disp if app.icon_url and app.save_icon else icon_op_ded, label=self.i18n['web.install.option.wicon.label']) @@ -517,13 +548,25 @@ def _ask_install_options(self, app: WebApplication, watcher: ProcessWatcher) -> check_options = MultipleSelectComponent(options=adv_opts, default_options=def_adv_opts, label=self.i18n['web.install.options.advanced'].capitalize()) - res = watcher.request_confirmation(title=self.i18n['web.install.options_dialog.title'], - body=None, - components=[form_1, check_options], - confirmation_label=self.i18n['continue'].capitalize(), - deny_label=self.i18n['cancel'].capitalize()) + install_ = watcher.request_confirmation(title=self.i18n['web.install.options_dialog.title'], + body=None, + components=[form_1, check_options], + confirmation_label=self.i18n['continue'].capitalize(), + deny_label=self.i18n['cancel'].capitalize()) + + if install_: + if not pre_validated: + typed_url = inp_url.get_value().strip() + + if not typed_url or not self._request_url(typed_url): + watcher.show_message(title=self.i18n['error'].capitalize(), + type_=MessageType.ERROR, + body=self.i18n['web.custom_action.install_app.invalid_url'].format(URL='(URL)', + url=bold(f'"{inp_url.get_value()}"'))) + return False, [] + else: + app.url = typed_url - if res: selected = [] if check_options.values: @@ -552,9 +595,9 @@ def _ask_install_options(self, app: WebApplication, watcher: ProcessWatcher) -> app.set_custom_icon(icon_chooser.file_path) selected.append('--icon={}'.format(icon_chooser.file_path)) - app.save_icon = inp_icon.value == icon_op_disp + app.save_icon = inp_icon.value == icon_op_disp if icon_op_disp else False - return res, selected + return True, selected return False, [] @@ -622,13 +665,9 @@ def _download_suggestion_icon(self, pkg: WebApplication, app_dir: str) -> Tuple[ pkg.name)) traceback.print_exc() - def install(self, pkg: WebApplication, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult: - continue_install, install_options = self._ask_install_options(pkg, watcher) + def _install(self, pkg: WebApplication, install_options: List[str], watcher: ProcessWatcher) -> TransactionResult: widevine_support = '--widevine' in install_options - if not continue_install: - return TransactionResult(success=False, installed=[], removed=[]) - watcher.change_substatus(self.i18n['web.env.checking']) handler = ProcessHandler(watcher) @@ -637,7 +676,8 @@ def install(self, pkg: WebApplication, root_password: str, disk_loader: DiskCach if web_config['environment']['system'] and not nativefier.is_available(): watcher.show_message(title=self.i18n['error'].capitalize(), - body=self.i18n['web.install.global_nativefier.unavailable'].format(n=bold('Nativefier'), app=bold(pkg.name)) + '.', + body=self.i18n['web.install.global_nativefier.unavailable'].format( + n=bold('Nativefier'), app=bold(pkg.name)) + '.', type_=MessageType.ERROR) return TransactionResult(success=False, installed=[], removed=[]) @@ -650,8 +690,9 @@ def install(self, pkg: WebApplication, root_password: str, disk_loader: DiskCach if comps_to_update and not self._ask_update_permission(comps_to_update, watcher): return TransactionResult(success=False, installed=[], removed=[]) - if not self.env_updater.update(components=comps_to_update, handler=handler): - watcher.show_message(title=self.i18n['error'], body=self.i18n['web.env.error'].format(bold(pkg.name)), type_=MessageType.ERROR) + if not self.env_updater.update(components=comps_to_update, handler=handler): + watcher.show_message(title=self.i18n['error'], body=self.i18n['web.env.error'].format(bold(pkg.name)), + type_=MessageType.ERROR) return TransactionResult(success=False, installed=[], removed=[]) Path(INSTALLED_PATH).mkdir(parents=True, exist_ok=True) @@ -661,17 +702,25 @@ def install(self, pkg: WebApplication, root_password: str, disk_loader: DiskCach app_dir = '{}/{}'.format(INSTALLED_PATH, app_id) watcher.change_substatus(self.i18n['web.install.substatus.checking_fixes']) - fix = self._get_fix_for(url_no_protocol=self._strip_url_protocol(pkg.url)) - fix_path = '{}/{}.js'.format(FIXES_PATH, app_id) + + electron_version = str(next((c for c in env_components if c.id == 'electron')).version) + + url_domain, electron_branch = self._strip_url_protocol(pkg.url), self._map_electron_branch(electron_version) + fix = self._get_fix_for(url_domain=url_domain, electron_branch=electron_branch) if fix: # just adding the fix as an installation option. The file will be written later - self.logger.info('Fix found for {}'.format(pkg.url)) - watcher.print('Fix found for {}'.format(pkg.url)) - install_options.append('--inject={}'.format(fix_path)) - Path(FIXES_PATH).mkdir(exist_ok=True, parents=True) + fix_log = f'Fix found for {pkg.url} (Electron: {electron_version})' + self.logger.info(fix_log) + watcher.print(fix_log) + + fix_path = FIX_FILE_PATH.format(app_id=pkg.id, electron_branch=electron_branch) + Path(os.path.dirname(fix_path)).mkdir(parents=True, exist_ok=True) - self.logger.info('Writting JS fix at {}'.format(fix_path)) + install_options.append(f'--inject={fix_path}') + Path(FIX_FILE_PATH).mkdir(exist_ok=True, parents=True) + + self.logger.info(f'Writing JS fix at {fix_path}') with open(fix_path, 'w+') as f: f.write(fix) @@ -684,7 +733,7 @@ def install(self, pkg: WebApplication, root_password: str, disk_loader: DiskCach icon_path, icon_bytes = download[0], download[1] pkg.custom_icon = icon_path - # writting the icon in a temporary folder to be used by the nativefier process + # writing the icon in a temporary folder to be used by the nativefier process temp_icon_path = '{}/{}'.format(TEMP_PATH, pkg.icon_url.split('/')[-1]) install_options.append('--icon={}'.format(temp_icon_path)) @@ -695,18 +744,34 @@ def install(self, pkg: WebApplication, root_password: str, disk_loader: DiskCach with open(temp_icon_path, 'wb+') as f: f.write(icon_bytes) + custom_props = self._get_custom_properties(url_domain=url_domain, electron_branch=electron_branch) + + if custom_props: + for prop, val in custom_props.items(): + if hasattr(pkg, prop): + try: + setattr(pkg, prop, val) + self.logger.info( + f"Using custom installation property '{prop}' ({val if val else ''}) for '{url_domain}' " + f"(Electron: {electron_version})") + except: + self.logger.error( + f"Could not set the custom installation property '{prop}' ({val if val else ''}) " + f"for '{url_domain}' (Electron: {electron_version})") + watcher.change_substatus(self.i18n['web.install.substatus.call_nativefier'].format(bold('nativefier'))) - electron_version = str(next((c for c in env_components if c.id == 'electron')).version) installed = handler.handle_simple(nativefier.install(url=pkg.url, name=app_id, output_dir=app_dir, electron_version=electron_version if not widevine_support else None, system=bool(web_config['environment']['system']), cwd=INSTALLED_PATH, + user_agent=pkg.user_agent, extra_options=install_options)) if not installed: msg = '{}.{}.'.format(self.i18n['wen.install.error'].format(bold(pkg.name)), - self.i18n['web.install.nativefier.error.unknown'].format(bold(self.i18n['details'].capitalize()))) + self.i18n['web.install.nativefier.error.unknown'].format( + bold(self.i18n['details'].capitalize()))) watcher.show_message(title=self.i18n['error'], body=msg, type_=MessageType.ERROR) return TransactionResult(success=False, installed=[], removed=[]) @@ -777,6 +842,27 @@ def install(self, pkg: WebApplication, root_password: str, disk_loader: DiskCach return TransactionResult(success=True, installed=[pkg], removed=[]) + def install(self, pkg: WebApplication, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult: + continue_install, install_options = self._ask_install_options(pkg, watcher, pre_validated=True) + + if not continue_install: + return TransactionResult(success=False, installed=[], removed=[]) + + return self._install(pkg, install_options, watcher) + + def install_app(self, root_password: str, watcher: ProcessWatcher) -> bool: + pkg = WebApplication() + continue_install, install_options = self._ask_install_options(pkg, watcher, pre_validated=False) + + if not continue_install: + return False + + if self._install(pkg, install_options, watcher).success: + self.serialize_to_disk(pkg, icon_bytes=None, only_icon=False) + return True + + return False + def _gen_desktop_entry_content(self, pkg: WebApplication) -> str: return """ [Desktop Entry] @@ -893,7 +979,8 @@ def _map_suggestion(self, suggestion: dict, env_settings: Optional[dict]) -> Pac icon_url=suggestion.get('icon_url'), categories=[suggestion['category']] if suggestion.get('category') else None, preset_options=suggestion.get('options'), - save_icon=suggestion.get('save_icon', False)) + save_icon=suggestion.get('save_icon', False), + user_agent=suggestion.get('user_agent')) app.set_version(suggestion.get('version')) @@ -1011,7 +1098,7 @@ def get_settings(self, screen_width: int, screen_height: int) -> Optional[ViewCo tooltip=self.i18n['web.settings.electron.version.tooltip'], placeholder='{}: 7.1.0'.format(self.i18n['example.short']), max_width=max_width, - id_='electron_version') + id_='electron_branch') native_opts = [ InputOption(label=self.i18n['web.settings.nativefier.env'].capitalize(), value=False, tooltip=self.i18n['web.settings.nativefier.env.tooltip'].format(app=self.context.app_name)), @@ -1053,7 +1140,7 @@ def save_settings(self, component: PanelComponent) -> Tuple[bool, Optional[List[ form_env = component.components[0] - web_config['environment']['electron']['version'] = str(form_env.get_component('electron_version').get_value()).strip() + web_config['environment']['electron']['version'] = str(form_env.get_component('electron_branch').get_value()).strip() if len(web_config['environment']['electron']['version']) == 0: web_config['environment']['electron']['version'] = None @@ -1073,5 +1160,24 @@ def save_settings(self, component: PanelComponent) -> Tuple[bool, Optional[List[ except: return False, [traceback.format_exc()] - def get_custom_actions(self) -> List[CustomSoftwareAction]: - return self.custom_actions + def gen_custom_actions(self) -> Generator[CustomSoftwareAction, None, None]: + if self._custom_actions is None: + self._custom_actions = ( + CustomSoftwareAction(i18n_label_key='web.custom_action.install_app', + i18n_status_key='web.custom_action.install_app.status', + manager=self, + manager_method='install_app', + icon_path=resource.get_path('img/web.svg', ROOT_DIR), + requires_root=False, + refresh=True, + requires_confirmation=False), + CustomSoftwareAction(i18n_label_key='web.custom_action.clean_env', + i18n_status_key='web.custom_action.clean_env.status', + manager=self, + manager_method='clean_environment', + icon_path=resource.get_path('img/web.svg', ROOT_DIR), + requires_root=False, + refresh=False) + ) + + yield from self._custom_actions diff --git a/bauh/gems/web/environment.py b/bauh/gems/web/environment.py index 0d105f8fb..edbc63d29 100644 --- a/bauh/gems/web/environment.py +++ b/bauh/gems/web/environment.py @@ -20,9 +20,8 @@ from bauh.commons.html import bold from bauh.commons.system import SimpleProcess, ProcessHandler from bauh.gems.web import ENV_PATH, NODE_DIR_PATH, NODE_BIN_PATH, NODE_MODULES_PATH, NATIVEFIER_BIN_PATH, \ - ELECTRON_CACHE_DIR, ELECTRON_DOWNLOAD_URL, ELECTRON_SHA256_URL, URL_ENVIRONMENT_SETTINGS, NPM_BIN_PATH, NODE_PATHS, \ - nativefier, ELECTRON_WIDEVINE_URL, ELECTRON_WIDEVINE_SHA256_URL, \ - ENVIRONMENT_SETTINGS_CACHED_FILE, ENVIRONMENT_SETTINGS_TS_FILE, get_icon_path, NATIVEFIER_BASE_URL + ELECTRON_CACHE_DIR, URL_ENVIRONMENT_SETTINGS, NPM_BIN_PATH, NODE_PATHS, \ + nativefier, ENVIRONMENT_SETTINGS_CACHED_FILE, ENVIRONMENT_SETTINGS_TS_FILE, get_icon_path from bauh.gems.web.model import WebApplication from bauh.view.util.translation import I18n @@ -49,7 +48,7 @@ def __init__(self, logger: logging.Logger, http_client: HttpClient, file_downloa self.task_read_settings_id = 'web_read_settings' self.taskman = taskman - def _download_and_install(self, version: str, version_url: str, watcher: ProcessWatcher) -> bool: + def _install_nodejs(self, version: str, version_url: str, watcher: ProcessWatcher) -> bool: self.logger.info(f"Downloading NodeJS {version}: {version_url}") tarf_path = f"{ENV_PATH}/{version_url.split('/')[-1]}" @@ -129,7 +128,7 @@ def update_node(self, version: str, version_url: str, watcher: ProcessWatcher = Path(ENV_PATH).mkdir(parents=True, exist_ok=True) if not os.path.exists(NODE_DIR_PATH): - return self._download_and_install(version=version, version_url=version_url, watcher=watcher) + return self._install_nodejs(version=version, version_url=version_url, watcher=watcher) else: installed_version = system.run_cmd('{} --version'.format(NODE_BIN_PATH), print_error=False) @@ -143,7 +142,7 @@ def update_node(self, version: str, version_url: str, watcher: ProcessWatcher = if version != installed_version: self.logger.info("The NodeJs installed version is different from the Cloud.") - return self._download_and_install(version=version, version_url=version_url, watcher=watcher) + return self._install_nodejs(version=version, version_url=version_url, watcher=watcher) else: self.logger.info("Node is already up to date") return True @@ -152,7 +151,7 @@ def update_node(self, version: str, version_url: str, watcher: ProcessWatcher = self.logger.info(f"Removing {NODE_DIR_PATH}") try: shutil.rmtree(NODE_DIR_PATH) - return self._download_and_install(version=version, version_url=version_url, watcher=watcher) + return self._install_nodejs(version=version, version_url=version_url, watcher=watcher) except: self.logger.error(f'Could not delete the dir {NODE_DIR_PATH}') return False @@ -191,20 +190,10 @@ def _install_nativefier(self, version: str, url: str, handler: ProcessHandler) - def _is_nativefier_installed(self) -> bool: return os.path.exists(NATIVEFIER_BIN_PATH) - def _get_electron_url(self, version: str, is_x86_x64_arch: bool, widevine: bool) -> str: - arch = 'x64' if is_x86_x64_arch else 'ia32' - if widevine: - return ELECTRON_WIDEVINE_URL.format(version=version, arch=arch) - else: - return ELECTRON_DOWNLOAD_URL.format(version=version, arch=arch) - - def _get_electron_sha256_url(self, version: str, widevine: bool) -> str: - if widevine: - return ELECTRON_WIDEVINE_SHA256_URL.format(version=version) - else: - return ELECTRON_SHA256_URL.format(version=version) + def _get_electron_url(self, version: str, base_url: str, is_x86_x64_arch: bool) -> str: + return base_url.format(version=version, arch='x64' if is_x86_x64_arch else 'ia32') - def check_electron_installed(self, version: str, is_x86_x64_arch: bool, widevine: bool) -> Dict[str, bool]: + def check_electron_installed(self, version: str, base_url: str, is_x86_x64_arch: bool, widevine: bool) -> Dict[str, bool]: self.logger.info(f"Checking if Electron {version} (widevine={widevine}) is installed") res = {'electron': False, 'sha256': False} @@ -214,7 +203,7 @@ def check_electron_installed(self, version: str, is_x86_x64_arch: bool, widevine files = {os.path.basename(f) for f in glob.glob(f'{ELECTRON_CACHE_DIR}/**', recursive=True) if os.path.isfile(f)} if files: - electron_url = self._get_electron_url(version, is_x86_x64_arch, widevine) + electron_url = self._get_electron_url(version=version, base_url=base_url, is_x86_x64_arch=is_x86_x64_arch) res['electron'] = os.path.basename(electron_url) in files res['sha256'] = res['electron'] else: @@ -304,6 +293,11 @@ def read_settings(self, web_config: dict, cache: bool = True) -> Optional[dict]: try: settings = yaml.safe_load(res.content) + nodejs_settings = settings.get('nodejs') + + if nodejs_settings: + nodejs_settings['url'] = nodejs_settings['url'].format(version=nodejs_settings['version']) + except yaml.YAMLError: self.logger.error(f'Could not parse environment settings: {res.text}') self._finish_task_download_settings() @@ -336,7 +330,8 @@ def read_settings(self, web_config: dict, cache: bool = True) -> Optional[dict]: return def _check_and_fill_electron(self, pkg: WebApplication, env: dict, local_config: dict, x86_x64: bool, widevine: bool, output: List[EnvironmentComponent]): - electron_version = env['electron-wvvmp' if widevine else 'electron']['version'] + electron_settings = env['electron-wvvmp' if widevine else 'electron'] + electron_version = electron_settings['version'] if not widevine and pkg.version and pkg.version != electron_version: # this feature does not support custom widevine electron at the moment self.logger.info(f'A preset Electron version is defined for {pkg.url}: {pkg.version}') @@ -346,9 +341,11 @@ def _check_and_fill_electron(self, pkg: WebApplication, env: dict, local_config: self.logger.warning(f"A custom Electron version will be used {electron_version} to install {pkg.url}") electron_version = local_config['environment']['electron']['version'] - electron_status = self.check_electron_installed(version=electron_version, is_x86_x64_arch=x86_x64, widevine=widevine) + electron_status = self.check_electron_installed(version=electron_version, base_url=electron_settings['url'], + is_x86_x64_arch=x86_x64, widevine=widevine) + + electron_url = self._get_electron_url(version=electron_version, base_url=electron_settings['url'], is_x86_x64_arch=x86_x64) - electron_url = self._get_electron_url(version=electron_version, is_x86_x64_arch=x86_x64, widevine=widevine) output.append(EnvironmentComponent(name=electron_url.split('/')[-1], version=electron_version, url=electron_url, @@ -357,7 +354,7 @@ def _check_and_fill_electron(self, pkg: WebApplication, env: dict, local_config: update=not electron_status['electron'], properties={'widevine': widevine})) - sha_url = self._get_electron_sha256_url(version=electron_version, widevine=widevine) + sha_url = electron_settings['sha_url'].format(version=electron_version) output.append(EnvironmentComponent(name=sha_url.split('/')[-1], version=electron_version, @@ -407,12 +404,7 @@ def _check_nativefier_installed(self, nativefier_settings: dict) -> bool: return True def _map_nativefier_file(self, nativefier_settings: dict) -> EnvironmentComponent: - base_url = nativefier_settings.get('url') - if not base_url: - self.logger.warning(f"'url' not found in nativefier environment settings. Using hardcoded URL '{NATIVEFIER_BASE_URL}'") - base_url = NATIVEFIER_BASE_URL - - url = base_url.format(version=nativefier_settings['version']) + url = nativefier_settings['url'].format(version=nativefier_settings['version']) return EnvironmentComponent(name=f"nativefier@{nativefier_settings['version']}", url=url, size=self.http_client.get_content_length(url), @@ -452,7 +444,7 @@ def update(self, components: List[EnvironmentComponent], handler: ProcessHandler nativefier_data = comp_map.get('nativefier') if node_data: - if not self._download_and_install(version=node_data.version, version_url=node_data.url, watcher=handler.watcher): + if not self._install_nodejs(version=node_data.version, version_url=node_data.url, watcher=handler.watcher): return False if not self._install_nativefier(version=nativefier_data.version, url=nativefier_data.url, handler=handler): diff --git a/bauh/gems/web/model.py b/bauh/gems/web/model.py index d04f2d474..58217f683 100644 --- a/bauh/gems/web/model.py +++ b/bauh/gems/web/model.py @@ -1,7 +1,7 @@ import glob import os from pathlib import Path -from typing import List +from typing import List, Optional from bauh import __app_name__ from bauh.api import user @@ -13,10 +13,12 @@ class WebApplication(SoftwarePackage): - def __init__(self, id: str = None, url: str = None, name: str = None, description: str = None, icon_url: str = None, - installation_dir: str = None, desktop_entry: str = None, installed: bool = False, version: str = None, - categories: List[str] = None, custom_icon: str = None, preset_options: List[str] = None, save_icon: bool = True, - options_set: List[str] = None, package_name: str = None, source_url: str = None): + def __init__(self, id: Optional[str] = None, url: Optional[str] = None, name: Optional[str] = None, description: Optional[str] = None, + icon_url: Optional[str] = None, installation_dir: Optional[str] = None, desktop_entry: Optional[str] = None, + installed: bool = False, version: Optional[str] = None, categories: Optional[List[str]] = None, + custom_icon: Optional[str] = None, preset_options: Optional[List[str]] = None, save_icon: bool = True, + options_set: Optional[List[str]] = None, package_name: Optional[str] = None, source_url: Optional[str] = None, + user_agent: Optional[str] = None): super(WebApplication, self).__init__(id=id if id else url, name=name, description=description, icon_url=icon_url, installed=installed, version=version, categories=categories) @@ -30,6 +32,7 @@ def __init__(self, id: str = None, url: str = None, name: str = None, descriptio self.package_name = package_name self.custom_icon = custom_icon self.set_custom_icon(custom_icon) + self.user_agent = user_agent def get_source_url(self): if self.source_url: @@ -50,7 +53,8 @@ def has_info(self): @staticmethod def _get_cached_attrs() -> tuple: return 'id', 'name', 'version', 'url', 'description', 'icon_url', 'installation_dir', \ - 'desktop_entry', 'categories', 'custom_icon', 'options_set', 'save_icon', 'package_name', 'source_url' + 'desktop_entry', 'categories', 'custom_icon', 'options_set', 'save_icon', 'package_name', 'source_url', \ + 'user_agent' def can_be_downgraded(self): return False diff --git a/bauh/gems/web/nativefier.py b/bauh/gems/web/nativefier.py index a1590a943..e6fcd215a 100644 --- a/bauh/gems/web/nativefier.py +++ b/bauh/gems/web/nativefier.py @@ -6,13 +6,17 @@ from bauh.gems.web import NATIVEFIER_BIN_PATH, NODE_PATHS, ELECTRON_CACHE_DIR -def install(url: str, name: str, output_dir: str, electron_version: Optional[str], cwd: str, system: bool, extra_options: List[str] = None) -> SimpleProcess: +def install(url: str, name: str, output_dir: str, electron_version: Optional[str], cwd: str, system: bool, + user_agent: Optional[str] = None, extra_options: List[str] = None) -> SimpleProcess: cmd = [NATIVEFIER_BIN_PATH if not system else 'nativefier', url, '--name', name, output_dir] if electron_version: cmd.append('-e') cmd.append(electron_version) + if user_agent: + cmd.extend(('--user-agent', user_agent)) + if extra_options: cmd.extend(extra_options) diff --git a/bauh/gems/web/npm.py b/bauh/gems/web/npm.py deleted file mode 100644 index 28e437a67..000000000 --- a/bauh/gems/web/npm.py +++ /dev/null @@ -1,5 +0,0 @@ -import shutil - - -def is_available() -> bool: - return bool(shutil.which('npm')) diff --git a/bauh/gems/web/resources/locale/ca b/bauh/gems/web/resources/locale/ca index 8132eedab..054ac126e 100644 --- a/bauh/gems/web/resources/locale/ca +++ b/bauh/gems/web/resources/locale/ca @@ -1,11 +1,14 @@ -gem.web.info=It allows to install Web applications on the system through their addresses ( URLs ) +gem.web.info=It allows to install Web applications on the system through their addresses (URL) gem.web.install.warning=This app it is not officially distributed by its domain owner web.custom_action.clean_env.failed=An error occurred during the installation environment cleaning web.custom_action.clean_env.status=Cleaning the installation environment web.custom_action.clean_env.success=Installation environment cleaned web.custom_action.clean_env=Clean installation environment +web.custom_action.install_app=Install Web application +web.custom_action.install_app.invalid_url=The address {URL} {url} could not be found. It is not possible to finish the installation. +web.custom_action.install_app.status=Installing Web application web.env.checking=Checking the Web installation environment -web.env.error=It seems there are issues with the Web installation environment. It wil not be possible to install {}. +web.env.error=It seems there are issues with the Web installation environment. It will not be possible to install {}. web.environment.install=Installing {} web.info.01_url=URL web.info.02_description=description @@ -57,7 +60,7 @@ web.install.option.widevine.label=Allow protected content web.install.option.widevine.tip=It allows the interaction with protected/encrypted content commonly used by streaming services (like videos, music, ...). It uses and alternative Electron version developed by castLabs. web.install.options.advanced=advanced web.install.options.basic=basic -web.install.options_dialog.title=Installation options +web.install.options_dialog.title=Web installation options web.install.substatus.call_nativefier=Running {} web.install.substatus.checking_fixes=Checking if there are published fixes web.install.substatus.options=Waiting for the installation options diff --git a/bauh/gems/web/resources/locale/en b/bauh/gems/web/resources/locale/en index 40d71c09b..51fb7888d 100644 --- a/bauh/gems/web/resources/locale/en +++ b/bauh/gems/web/resources/locale/en @@ -1,11 +1,14 @@ -gem.web.info=It allows to install Web applications on the system through their addresses ( URLs ) +gem.web.info=It allows to install Web applications on the system through their addresses (URL) gem.web.install.warning=This app it is not officially distributed by its domain owner web.custom_action.clean_env.failed=An error occurred during the installation environment cleaning web.custom_action.clean_env.status=Cleaning the installation environment web.custom_action.clean_env.success=Installation environment cleaned web.custom_action.clean_env=Clean installation environment +web.custom_action.install_app=Install Web application +web.custom_action.install_app.invalid_url=The address {URL} {url} could not be found. It is not possible to finish the installation. +web.custom_action.install_app.status=Installing Web application web.env.checking=Checking the Web installation environment -web.env.error=It seems there are issues with the Web installation environment. It wil not be possible to install {}. +web.env.error=It seems there are issues with the Web installation environment. It will not be possible to install {}. web.environment.install=Installing {} web.info.01_url=URL web.info.02_description=description @@ -57,7 +60,7 @@ web.install.option.widevine.label=Allow protected content web.install.option.widevine.tip=It allows the interaction with protected/encrypted content commonly used by streaming services (like videos, music, ...). It uses and alternative Electron version developed by castLabs. web.install.options.advanced=advanced web.install.options.basic=basic -web.install.options_dialog.title=Installation options +web.install.options_dialog.title=Web installation options web.install.substatus.call_nativefier=Running {} web.install.substatus.checking_fixes=Checking if there are published fixes web.install.substatus.options=Waiting for the installation options diff --git a/bauh/gems/web/resources/locale/es b/bauh/gems/web/resources/locale/es index 2d9f61a0d..cae60d756 100644 --- a/bauh/gems/web/resources/locale/es +++ b/bauh/gems/web/resources/locale/es @@ -1,9 +1,12 @@ -gem.web.info=Le permite instalar aplicaciones web a través de sus direcciones (URLs) +gem.web.info=Le permite instalar aplicaciones web a través de sus direcciones (URL) gem.web.install.warning=Esta aplicación no es distribuida oficialmente por el propietario del dominio web.custom_action.clean_env.failed=Se produjo un error durante la limpieza del ambiente de instalación web.custom_action.clean_env.status=Limpiando el ambiente de instalación web.custom_action.clean_env.success=Ambiente de instalación limpio web.custom_action.clean_env=Limpiar ambiente de instalación +web.custom_action.install_app=Instalar aplicación Web +web.custom_action.install_app.invalid_url=No se pudo encontrar la dirección {URL} {url}. No es posible finalizar la instalación. +web.custom_action.install_app.status=Instalando aplicación Web web.env.checking=Verificando el ambiente de instalación web web.env.error=Parece que hay problemas con el ambiente de instalación web. No será posible instalar {}. web.environment.install=Instalando {} @@ -57,7 +60,7 @@ web.install.option.widevine.label=Permitir contenido protegido web.install.option.widevine.tip=Permite la interacción con contenido protegido/encriptado comúnmente utilizado por los servicios de transmisión (como videos, música, ...). Utiliza una versión alternativa de Electron desarrollada por castLabs. web.install.options.advanced=avanzadas web.install.options.basic=básicas -web.install.options_dialog.title=Opciones de instalación +web.install.options_dialog.title=Opciones de instalación Web web.install.substatus.call_nativefier=Ejecutando {} web.install.substatus.checking_fixes=Verificando si hay correcciones publicadas web.install.substatus.options=Esperando las opciones de instalación diff --git a/bauh/gems/web/resources/locale/fr b/bauh/gems/web/resources/locale/fr index 25674c980..d96e414ab 100644 --- a/bauh/gems/web/resources/locale/fr +++ b/bauh/gems/web/resources/locale/fr @@ -1,9 +1,12 @@ -gem.web.info=Permet d'installer des applications Web sur le système depuis leur adresses ( URLs ) +gem.web.info=Permet d'installer des applications Web sur le système depuis leur adresses (URL) gem.web.install.warning=Cette application n'est pas officiellement distribué par le propiétaire de son domaine web.custom_action.clean_env.failed=Une erreur est survenue lors du nettoyage de l'environnement d'installation web.custom_action.clean_env.status=Nettoyage de l'environnement d'installation web.custom_action.clean_env.success=Environnement d'installation nettoyé web.custom_action.clean_env=Nettoyage de l'environnement d'installation Web. +web.custom_action.install_app=Install Web application +web.custom_action.install_app.invalid_url=The address {URL} {url} could not be found. It is not possible to finish the installation. +web.custom_action.install_app.status=Installing Web application web.env.checking=Verification de l'environnement d'installation Web. web.env.error=Il y a des problèmes avec l'environnement d'installation Web. {} ne pourra pas être installé. web.environment.install=Intallation de {} @@ -57,7 +60,7 @@ web.install.option.widevine.label=Allow protected content web.install.option.widevine.tip=It allows the interaction with protected/encrypted content commonly used by streaming services (like videos, music, ...). It uses and alternative Electron version developed by castLabs. web.install.options.advanced=avancée web.install.options.basic=basique -web.install.options_dialog.title=Options d'installation +web.install.options_dialog.title=Options d'installation Web web.install.substatus.call_nativefier=Lancement {} web.install.substatus.checking_fixes=Verification de nouveaux correctifs disponibles web.install.substatus.options=En attente des options d'installation diff --git a/bauh/gems/web/resources/locale/it b/bauh/gems/web/resources/locale/it index 8132eedab..cf57e87fa 100644 --- a/bauh/gems/web/resources/locale/it +++ b/bauh/gems/web/resources/locale/it @@ -1,9 +1,12 @@ -gem.web.info=It allows to install Web applications on the system through their addresses ( URLs ) +gem.web.info=It allows to install Web applications on the system through their addresses (URL) gem.web.install.warning=This app it is not officially distributed by its domain owner web.custom_action.clean_env.failed=An error occurred during the installation environment cleaning web.custom_action.clean_env.status=Cleaning the installation environment web.custom_action.clean_env.success=Installation environment cleaned web.custom_action.clean_env=Clean installation environment +web.custom_action.install_app=Install Web application +web.custom_action.install_app.invalid_url=The address {URL} {url} could not be found. It is not possible to finish the installation. +web.custom_action.install_app.status=Installing Web application web.env.checking=Checking the Web installation environment web.env.error=It seems there are issues with the Web installation environment. It wil not be possible to install {}. web.environment.install=Installing {} @@ -57,7 +60,7 @@ web.install.option.widevine.label=Allow protected content web.install.option.widevine.tip=It allows the interaction with protected/encrypted content commonly used by streaming services (like videos, music, ...). It uses and alternative Electron version developed by castLabs. web.install.options.advanced=advanced web.install.options.basic=basic -web.install.options_dialog.title=Installation options +web.install.options_dialog.title=Web installation options web.install.substatus.call_nativefier=Running {} web.install.substatus.checking_fixes=Checking if there are published fixes web.install.substatus.options=Waiting for the installation options diff --git a/bauh/gems/web/resources/locale/pt b/bauh/gems/web/resources/locale/pt index 96ad226a1..9488505be 100644 --- a/bauh/gems/web/resources/locale/pt +++ b/bauh/gems/web/resources/locale/pt @@ -1,9 +1,12 @@ -gem.web.info=Permite instalar aplicações Web através dos seus endereços ( URLs ) +gem.web.info=Permite instalar aplicações Web através dos seus endereços (URL) gem.web.install.warning=Esse aplicativo não é oficialmente distribuído pelo proprietário do domínio web.custom_action.clean_env.failed=Ocorreu um problema durante a limpeza do ambiente de instalação web.custom_action.clean_env.status=Limpando ambiente de instalação web.custom_action.clean_env.success=Ambiente de instalação limpo web.custom_action.clean_env=Limpar ambiente de instalação +web.custom_action.install_app=Instalar aplicação Web +web.custom_action.install_app.invalid_url=O endereço {URL} {url} não foi encontrado. Não é possível finalizar a instalação. +web.custom_action.install_app.status=Instalando aplicação Web web.env.checking=Verificando o ambiente de instalação Web web.env.error=Parce que existem problemas com o ambiente de instalação Web. Não será possível instalar {}. web.environment.nativefier=Instalando {} @@ -56,7 +59,7 @@ web.install.option.widevine.label=Permitir conteúdo protegido web.install.option.widevine.tip=Permite a interação com conteúdo protegido/criptografado comumente utilizados por serviços de streaming (como videos, música, ...). Utiliza uma versão alternativa do Electron desenvolvida pela castLabs. web.install.options.advanced=avançadas web.install.options.basic=básicas -web.install.options_dialog.title=Opções de instalação +web.install.options_dialog.title=Opções de instalação Web web.install.substatus.call_nativefier=Executando {} web.install.substatus.checking_fixes=Verificando se há correções publicadas web.install.substatus.options=Aguardando as opções de instalação diff --git a/bauh/gems/web/resources/locale/ru b/bauh/gems/web/resources/locale/ru index c8224f9f7..626ca9393 100644 --- a/bauh/gems/web/resources/locale/ru +++ b/bauh/gems/web/resources/locale/ru @@ -4,6 +4,9 @@ web.custom_action.clean_env=Очистка среды установки web.custom_action.clean_env.failed=Произошла ошибка при очистке среды установки web.custom_action.clean_env.status=Среда установки будет очищена web.custom_action.clean_env.success=Среда установки очищена! +web.custom_action.install_app=Install Web application +web.custom_action.install_app.invalid_url=The address {URL} {url} could not be found. It is not possible to finish the installation. +web.custom_action.install_app.status=Installing Web application web.env.checking=Проверка Веб-среды web.env.error=Проблемы с Веб-средой. Невозможно установить {}. web.environment.install=Устанавливается {} diff --git a/bauh/gems/web/resources/locale/tr b/bauh/gems/web/resources/locale/tr index 41120cf48..94552e8c7 100644 --- a/bauh/gems/web/resources/locale/tr +++ b/bauh/gems/web/resources/locale/tr @@ -4,6 +4,9 @@ web.custom_action.clean_env.failed=Kurulum ortamı temizliği sırasında bir ha web.custom_action.clean_env.status=Kurulum ortamını temizleme web.custom_action.clean_env.success=Kurulum ortamı temizlendi web.custom_action.clean_env=Temiz kurulum ortamı +web.custom_action.install_app=Install Web application +web.custom_action.install_app.invalid_url=The address {URL} {url} could not be found. It is not possible to finish the installation. +web.custom_action.install_app.status=Installing Web application web.env.checking=Web kurulum ortamını kontrol et web.env.error=Görünüşe göre Web kurulum ortamıyla ilgili sorunlar var. {} kurmak mümkün olmayacak. web.environment.install={} yükleniyor diff --git a/bauh/view/core/controller.py b/bauh/view/core/controller.py index ac889b105..6cd470bce 100755 --- a/bauh/view/core/controller.py +++ b/bauh/view/core/controller.py @@ -4,7 +4,7 @@ import traceback from subprocess import Popen, STDOUT from threading import Thread -from typing import List, Set, Type, Tuple, Dict, Optional +from typing import List, Set, Type, Tuple, Dict, Optional, Generator, Callable from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext, UpgradeRequirements, \ UpgradeRequirement, TransactionResult, SoftwareAction @@ -54,20 +54,21 @@ def __init__(self, managers: List[SoftwareManager], context: ApplicationContext, self.settings_manager = settings_manager self.http_client = context.http_client self.configman = CoreConfigManager() - self.extra_actions = [CustomSoftwareAction(i18n_label_key='action.reset', + self.extra_actions = (CustomSoftwareAction(i18n_label_key='action.reset', i18n_status_key='action.reset.status', manager_method='reset', manager=self, icon_path=resource.get_path('img/logo.svg'), requires_root=False, - refresh=False)] - self.dynamic_extra_actions = {CustomSoftwareAction(i18n_label_key='action.backups', - i18n_status_key='action.backups.status', - manager_method='launch_timeshift', - manager=self, - icon_path='timeshift', - requires_root=False, - refresh=False): self.is_backups_action_available} + refresh=False),) + self.dynamic_extra_actions: Dict[CustomSoftwareAction, Callable[[dict], bool]] = { + CustomSoftwareAction(i18n_label_key='action.backups', + i18n_status_key='action.backups.status', + manager_method='launch_timeshift', + manager=self, + icon_path='timeshift', + requires_root=False, + refresh=False): self.is_backups_action_available} def _is_timeshift_launcher_available(self) -> bool: return bool(shutil.which('timeshift-launcher')) @@ -161,14 +162,14 @@ def search(self, words: str, disk_loader: DiskCacheLoader = None, limit: int = - if self.context.is_internet_available(): norm_word = words.strip().lower() - url_words = RE_IS_URL.match(norm_word) + is_url = bool(RE_IS_URL.match(norm_word)) disk_loader = self.disk_loader_factory.new() disk_loader.start() threads = [] for man in self.managers: - t = Thread(target=self._search, args=(norm_word, url_words, man, disk_loader, res)) + t = Thread(target=self._search, args=(norm_word, is_url, man, disk_loader, res)) t.start() threads.append(t) @@ -611,8 +612,7 @@ def reset(self, root_password: str, watcher: ProcessWatcher) -> bool: return True - def get_custom_actions(self) -> List[CustomSoftwareAction]: - actions = [] + def gen_custom_actions(self) -> Generator[CustomSoftwareAction, None, None]: if self.managers: working_managers = [] @@ -624,20 +624,17 @@ def get_custom_actions(self) -> List[CustomSoftwareAction]: working_managers.sort(key=lambda m: m.__class__.__name__) for man in working_managers: - man_actions = man.get_custom_actions() - - if man_actions: - actions.extend(man_actions) + for action in man.gen_custom_actions(): + yield action app_config = self.configman.get_config() for action, available in self.dynamic_extra_actions.items(): if available(app_config): - actions.append(action) - - actions.extend(self.extra_actions) + yield action - return actions + for action in self.extra_actions: + yield action def _fill_sizes(self, man: SoftwareManager, pkgs: List[SoftwarePackage]): ti = time.time() diff --git a/bauh/view/core/settings.py b/bauh/view/core/settings.py index 4244070b6..88820a550 100644 --- a/bauh/view/core/settings.py +++ b/bauh/view/core/settings.py @@ -208,7 +208,7 @@ def _gen_ui_settings(self, core_config: dict, screen_width: int, screen_height: select_scale = RangeInputComponent(id_="scalef", label=self.i18n['core.config.ui.scale_factor'] + ' (%)', tooltip=self.i18n['core.config.ui.scale_factor.tip'], - min_value=100, max_value=400, step_value=5, value=scale * 100, + min_value=100, max_value=400, step_value=5, value=int(scale * 100), max_width=default_width) cur_style = QApplication.instance().property('qt_style') if not core_config['ui']['qt_style'] else core_config['ui']['qt_style'] diff --git a/bauh/view/qt/apps_table.py b/bauh/view/qt/apps_table.py index 712e61f4b..92afff82b 100644 --- a/bauh/view/qt/apps_table.py +++ b/bauh/view/qt/apps_table.py @@ -104,7 +104,7 @@ def has_any_settings(self, pkg: PackageView): return pkg.model.has_history() or \ pkg.model.can_be_downgraded() or \ pkg.model.supports_ignored_updates() or \ - bool(pkg.model.get_custom_supported_actions()) + bool(pkg.model.get_custom_actions()) def show_pkg_actions(self, pkg: PackageView): menu_row = QMenu() @@ -154,8 +154,9 @@ def ignore_updates(): button_name=button_name, action=ignore_updates)) - if bool(pkg.model.get_custom_supported_actions()): - actions = [self._map_custom_action(pkg, a, menu_row) for a in pkg.model.get_custom_supported_actions()] + custom_actions = pkg.model.get_custom_actions() + if custom_actions: + actions = [self._map_custom_action(pkg, a, menu_row) for a in custom_actions] menu_row.addActions(actions) menu_row.adjustSize() @@ -169,10 +170,10 @@ def custom_action(): else: body = '{} ?'.format(self.i18n[action.i18n_label_key]) - if ConfirmationDialog(icon=QIcon(pkg.model.get_type_icon_path()), - title=self.i18n[action.i18n_label_key], - body=self._parag(body), - i18n=self.i18n).ask(): + if not action.requires_confirmation or ConfirmationDialog(icon=QIcon(pkg.model.get_type_icon_path()), + title=self.i18n[action.i18n_label_key], + body=self._parag(body), + i18n=self.i18n).ask(): self.window.begin_execute_custom_action(pkg, action) return QCustomMenuAction(parent=parent, diff --git a/bauh/view/qt/dialog.py b/bauh/view/qt/dialog.py index 0ada9b8a3..2de9973ca 100644 --- a/bauh/view/qt/dialog.py +++ b/bauh/view/qt/dialog.py @@ -65,8 +65,7 @@ def __init__(self, title: str, body: Optional[str], i18n: I18n, icon: QIcon = QI self.layout().addWidget(container_body) lb_icon = QLabel() - lb_icon.setObjectName('confirm_icon') - lb_icon.setPixmap(QApplication.style().standardIcon(QStyle.SP_MessageBoxQuestion).pixmap(QSize(48, 48))) + lb_icon.setObjectName("confirm_dialog_icon") container_body.layout().addWidget(lb_icon) if body: diff --git a/bauh/view/qt/prepare.py b/bauh/view/qt/prepare.py index 1439dd1f9..71ee58b79 100644 --- a/bauh/view/qt/prepare.py +++ b/bauh/view/qt/prepare.py @@ -230,7 +230,7 @@ def __init__(self, context: ApplicationContext, manager: SoftwareManager, screen self.layout().addWidget(self.bottom_widget) self.bottom_widget.setVisible(False) - self.bt_bar = QCustomToolbar() + self.bt_bar = QCustomToolbar(policy_height=QSizePolicy.Fixed) self.bt_close = QPushButton(self.i18n['close'].capitalize()) self.bt_close.setObjectName('bt_cancel') self.bt_close.setCursor(QCursor(Qt.PointingHandCursor)) diff --git a/bauh/view/qt/systray.py b/bauh/view/qt/systray.py index 5014ee6c9..1f72020e8 100755 --- a/bauh/view/qt/systray.py +++ b/bauh/view/qt/systray.py @@ -25,28 +25,30 @@ from bauh.view.util import util, resource from bauh.view.util.translation import I18n +CLI_NAME = f'{__app_name__}-cli' + def get_cli_path() -> str: venv = os.getenv('VIRTUAL_ENV') if venv: - cli_path = '{}/bin/bauh-cli'.format(venv) + cli_path = f'{venv}/bin/{CLI_NAME}' if os.path.exists(cli_path): return cli_path elif not sys.executable.startswith('/usr'): - cli_path = '{}/bin/bauh-cli'.format(sys.prefix) + cli_path = f'{sys.prefix}/bin/{CLI_NAME}' if os.path.exists(cli_path): return cli_path else: - return shutil.which('bauh-cli') + return shutil.which(CLI_NAME) def list_updates(logger: logging.Logger) -> List[PackageUpdate]: cli_path = get_cli_path() if cli_path: - output = run_cmd('{} updates -f json'.format(cli_path)) + output = run_cmd(f'{cli_path} updates -f json') if output: return [PackageUpdate(pkg_id=o['id'], name=o['name'], version=o['version'], pkg_type=o['type']) for o in json.loads(output)] @@ -54,7 +56,7 @@ def list_updates(logger: logging.Logger) -> List[PackageUpdate]: logger.info("No updates found") else: - logger.warning('bauh-cli seems not to be installed') + logger.warning(f'"{CLI_NAME}" seems not to be installed') return [] @@ -186,7 +188,7 @@ def __init__(self, config: dict, screen_size: QSize, logger: logging.Logger, man self.set_default_tooltip() def set_default_tooltip(self): - self.setToolTip('{} ({})'.format(self.i18n['tray.action.manage'], __app_name__).lower()) + self.setToolTip(f"{self.i18n['tray.action.manage']} ({__app_name__})".lower()) def handle_click(self, reason): if reason == self.Trigger: @@ -203,8 +205,8 @@ def notify_updates(self, updates: List[PackageUpdate], notify_user: bool = True) try: if len(updates) > 0: - self.logger.info("{} updates available".format(len(updates))) - update_keys = {'{}:{}:{}'.format(up.type, up.id, up.version) for up in updates} + self.logger.info(f"{len(updates)} updates available") + update_keys = {f'{up.type}:{up.id}:{up.version}' for up in updates} new_icon = self.icon_updates @@ -220,11 +222,11 @@ def notify_updates(self, updates: List[PackageUpdate], notify_user: bool = True) ups_by_type[ptype] = count msg = StringIO() - msg.write(self.i18n['notification.update{}'.format('' if n_updates == 1 else 's')].format(n_updates)) + msg.write(self.i18n[f"notification.update{'' if n_updates == 1 else 's'}"].format(n_updates)) if len(ups_by_type) > 1: for ptype in sorted(ups_by_type): - msg.write('\n * {} ( {} )'.format(ptype, ups_by_type[ptype])) + msg.write(f'\n * {ptype} ({ups_by_type[ptype]})') msg.seek(0) msg = msg.read() @@ -246,14 +248,14 @@ def notify_updates(self, updates: List[PackageUpdate], notify_user: bool = True) def show_manage_window(self): if self.manage_process is None: - self.manage_process = Popen([sys.executable, '{}/app.py'.format(ROOT_DIR)]) + self.manage_process = Popen([sys.executable, f'{ROOT_DIR}/app.py']) elif self.manage_process.poll() is not None: # it means it has finished self.manage_process = None self.show_manage_window() def show_settings_window(self): if self.settings_process is None: - self.settings_process = Popen([sys.executable, '{}/app.py'.format(ROOT_DIR), '--settings']) + self.settings_process = Popen([sys.executable, f'{ROOT_DIR}/app.py', '--settings']) elif self.settings_process.poll() is not None: # it means it has finished self.settings_process = None self.show_settings_window() diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py index 9b36ca3c0..7356cfbfa 100644 --- a/bauh/view/qt/thread.py +++ b/bauh/view/qt/thread.py @@ -1025,7 +1025,7 @@ def run(self): res = {'success': False, 'pkg': self.pkg, 'action': self.custom_action, 'error': None, 'error_type': MessageType.ERROR} if self.custom_action.backup: - proceed, _ = self.request_backup(app_config=CoreConfigManager.get_config(), + proceed, _ = self.request_backup(app_config=CoreConfigManager().get_config(), action_key=None, i18n=self.i18n, root_password=self.root_pwd, diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py index bc4a438e2..f7064c280 100755 --- a/bauh/view/qt/window.py +++ b/bauh/view/qt/window.py @@ -373,7 +373,7 @@ def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager self.container_bottom.layout().addWidget(bt_themes) self.comp_manager.register_component(BT_THEMES, bt_themes) - self.custom_actions = manager.get_custom_actions() + self.custom_actions = [a for a in manager.gen_custom_actions()] bt_custom_actions = IconButton(action=self.show_custom_actions, i18n=self.i18n, tooltip=self.i18n['manage_window.bt_custom_actions.tip']) @@ -460,7 +460,7 @@ def _register_groups(self): self.comp_manager.register_group(GROUP_LOWER_BTS, False, BT_SUGGESTIONS, BT_THEMES, BT_CUSTOM_ACTIONS, BT_SETTINGS, BT_ABOUT) def update_custom_actions(self): - self.custom_actions = self.manager.get_custom_actions() + self.custom_actions = [a for a in self.manager.gen_custom_actions()] def _update_process_progress(self, val: int): if self.progress_controll_enabled: @@ -1076,7 +1076,7 @@ def _resize(self, accept_lower_width: bool = True): new_width *= 1.05 # this extra size is not because of the toolbar button, but the table upgrade buttons if (self.pkgs and accept_lower_width) or new_width > self.width(): - self.resize(new_width, self.height()) + self.resize(int(new_width), self.height()) def set_progress_controll(self, enabled: bool): self.progress_controll_enabled = enabled @@ -1422,10 +1422,11 @@ def _update_progress(self, value: int): self.progress_bar.setValue(value) def begin_execute_custom_action(self, pkg: Optional[PackageView], action: CustomSoftwareAction): - if pkg is None and not ConfirmationDialog(title=self.i18n['confirmation'].capitalize(), - body='

{}

'.format(self.i18n['custom_action.proceed_with'].capitalize().format(bold(self.i18n[action.i18n_label_key]))), - icon=QIcon(action.icon_path) if action.icon_path else QIcon(resource.get_path('img/logo.svg')), - i18n=self.i18n).ask(): + if pkg is None and action.requires_confirmation and \ + not ConfirmationDialog(title=self.i18n['confirmation'].capitalize(), + body='

{}

'.format(self.i18n['custom_action.proceed_with'].capitalize().format(bold(self.i18n[action.i18n_label_key]))), + icon=QIcon(action.icon_path) if action.icon_path else QIcon(resource.get_path('img/logo.svg')), + i18n=self.i18n).ask(): return False pwd = None diff --git a/bauh/view/resources/locale/ca b/bauh/view/resources/locale/ca index c8707435b..467c96139 100644 --- a/bauh/view/resources/locale/ca +++ b/bauh/view/resources/locale/ca @@ -346,6 +346,7 @@ manage_window.upgrade_all.popup.body=Voleu actualitzar totes les aplicacions sel manage_window.upgrade_all.popup.title=Actualitza message.file.not_exist=Fitxer no existeix message.file.not_exist.body=El fitxer {} sembla no existir +message.requires_architecture=Requires {arch} architecture mirror=mirall missing_dep={dep} is not installed name=nom diff --git a/bauh/view/resources/locale/de b/bauh/view/resources/locale/de index dda94c790..bf8cd3e1c 100644 --- a/bauh/view/resources/locale/de +++ b/bauh/view/resources/locale/de @@ -345,6 +345,7 @@ manage_window.upgrade_all.popup.body=Alle ausgewählten Anwendungen upgraden manage_window.upgrade_all.popup.title=Upgrade message.file.not_exist=Datei existiert nicht message.file.not_exist.body=Die Datei {} scheint nicht zu existieren +message.requires_architecture=Requires {arch} architecture mirror=mirror missing_dep={dep} is not installed name=Name diff --git a/bauh/view/resources/locale/en b/bauh/view/resources/locale/en index c4668f631..2602b3cae 100644 --- a/bauh/view/resources/locale/en +++ b/bauh/view/resources/locale/en @@ -346,6 +346,7 @@ manage_window.upgrade_all.popup.body=Upgrade all selected applications ? manage_window.upgrade_all.popup.title=Upgrade message.file.not_exist.body=The file {} seems not to exist message.file.not_exist=File does not exist +message.requires_architecture=Requires {arch} architecture mirror=mirror missing_dep={dep} is not installed name=name diff --git a/bauh/view/resources/locale/es b/bauh/view/resources/locale/es index 8f27bbb2d..d8fa3f36e 100644 --- a/bauh/view/resources/locale/es +++ b/bauh/view/resources/locale/es @@ -347,6 +347,7 @@ manage_window.upgrade_all.popup.body=¿Quiere actualizar todas las aplicaciones manage_window.upgrade_all.popup.title=Actualizar message.file.not_exist=Archivo no existe message.file.not_exist.body=El archivo {} parece no existir +message.requires_architecture=Requiere arquitectura {arch} mirror=espejo missing_dep={dep} no está instalado name=nombre diff --git a/bauh/view/resources/locale/fr b/bauh/view/resources/locale/fr index c94b08366..c9ce6f020 100644 --- a/bauh/view/resources/locale/fr +++ b/bauh/view/resources/locale/fr @@ -342,6 +342,7 @@ manage_window.upgrade_all.popup.body=Mettre à jour toutes applications sélecti manage_window.upgrade_all.popup.title=Mettre à jour message.file.not_exist.body=Le fichier {} n'a pas l'air d'exister message.file.not_exist=Fichier inexistant +message.requires_architecture=Requires {arch} architecture mirror=mirroir missing_dep={dep} is not installed name=nom diff --git a/bauh/view/resources/locale/it b/bauh/view/resources/locale/it index 6bf8c1b3a..c9e342e1c 100644 --- a/bauh/view/resources/locale/it +++ b/bauh/view/resources/locale/it @@ -347,6 +347,7 @@ manage_window.upgrade_all.popup.body=Aggiornare tutte le applicazioni selezionat manage_window.upgrade_all.popup.title=Aggiorna message.file.not_exist=File non esiste message.file.not_exist.body=Il file {} sembra non esistere +message.requires_architecture=Requires {arch} architecture mirror=specchio missing_dep={dep} is not installed name=nome diff --git a/bauh/view/resources/locale/pt b/bauh/view/resources/locale/pt index a8a3510e6..70c2da4c5 100644 --- a/bauh/view/resources/locale/pt +++ b/bauh/view/resources/locale/pt @@ -346,6 +346,7 @@ manage_window.upgrade_all.popup.body=Atualizar todos os aplicativos selecionados manage_window.upgrade_all.popup.title=Atualizar message.file.not_exist.body=O arquivo {} parece não existir message.file.not_exist=Arquivo não existe +message.requires_architecture=Requer arquitetura {arch} mirror=espelho missing_dep={dep} não está instalado name=nome diff --git a/bauh/view/resources/locale/ru b/bauh/view/resources/locale/ru index db9188e28..8bdc8fc97 100644 --- a/bauh/view/resources/locale/ru +++ b/bauh/view/resources/locale/ru @@ -345,6 +345,7 @@ manage_window.upgrade_all.popup.body=Обновить все выбранные manage_window.upgrade_all.popup.title=Обновление message.file.not_exist=Файл не существует message.file.not_exist.body=Файл {}, кажется, не существует +message.requires_architecture=Requires {arch} architecture mirror=Зеркало missing_dep={dep} is not installed name=Имя diff --git a/bauh/view/resources/locale/tr b/bauh/view/resources/locale/tr index 6250f3d1f..0068db5d5 100644 --- a/bauh/view/resources/locale/tr +++ b/bauh/view/resources/locale/tr @@ -345,6 +345,7 @@ manage_window.upgrade_all.popup.body=Seçilen tüm uygulamalar yükseltilsin mi manage_window.upgrade_all.popup.title=Yükselt message.file.not_exist.body={} Dosyası yok gibi görünüyor message.file.not_exist=Dosya bulunamuyor +message.requires_architecture=Requires {arch} architecture mirror=yansı missing_dep={dep} is not installed name=isim diff --git a/bauh/view/resources/style/darcula/darcula.qss b/bauh/view/resources/style/darcula/darcula.qss index 9996ec5f7..cb948165d 100644 --- a/bauh/view/resources/style/darcula/darcula.qss +++ b/bauh/view/resources/style/darcula/darcula.qss @@ -409,7 +409,7 @@ PackagesTable QToolButton#bt_uninstall:disabled { } QLabel[help_icon = "true"] { - qproperty-pixmap: url("@style_dir/img/help.svg"); + qproperty-pixmap: url("@style_dir/img/question.svg"); } QLabel[warning_icon = "true"] { @@ -417,7 +417,11 @@ QLabel[warning_icon = "true"] { } QLabel[tip_icon = "true"] { - qproperty-pixmap: url("@style_dir/img/help.svg"); + qproperty-pixmap: url("@style_dir/img/question.svg"); +} + +QLabel#confirm_dialog_icon { + qproperty-pixmap: url("@style_dir/img/question.svg"); } FormQt IconButton#clean_field { diff --git a/bauh/view/resources/style/darcula/img/help.svg b/bauh/view/resources/style/darcula/img/question.svg similarity index 99% rename from bauh/view/resources/style/darcula/img/help.svg rename to bauh/view/resources/style/darcula/img/question.svg index 29aafd06d..9b1e54b1a 100644 --- a/bauh/view/resources/style/darcula/img/help.svg +++ b/bauh/view/resources/style/darcula/img/question.svg @@ -15,7 +15,7 @@ height="512" viewBox="0 0 512 512" xml:space="preserve" - sodipodi:docname="help.svg" + sodipodi:docname="question.svg" inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">image/svg+xmlimage/svg+xmlimage/svg+xml