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 '{}
'.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)">