[web] feature: new custom action button to install apps

This commit is contained in:
Vinicius Moreira
2021-12-15 13:40:20 -03:00
parent cbc52b68ee
commit 8576a1f169
11 changed files with 117 additions and 37 deletions

View File

@@ -5,6 +5,13 @@ 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/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [0.9.24] ## [0.9.24]
### 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)
<p align="center">
<img src="https://raw.githubusercontent.com/vinifmor/bauh-files/master/pictures/releases/0.9.24/install_web_app.png">
</p>
### Improvements ### Improvements
- General - General
- handling http redirect errors - handling http redirect errors

View File

@@ -56,6 +56,9 @@ class HttpClient:
elif isinstance(e, requests.exceptions.TooManyRedirects): elif isinstance(e, requests.exceptions.TooManyRedirects):
self.logger.warning(f"Too many redirects for GET -> {url}") self.logger.warning(f"Too many redirects for GET -> {url}")
raise e 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)) self.logger.error("Could not retrieve data from '{}'".format(url))
traceback.print_exc() traceback.print_exc()

View File

@@ -14,7 +14,7 @@ from typing import List, Type, Set, Tuple, Optional, Dict
import requests import requests
import yaml import yaml
from colorama import Fore from colorama import Fore
from requests import exceptions, Response from requests import Response
from bauh.api.abstract.context import ApplicationContext from bauh.api.abstract.context import ApplicationContext
from bauh.api.abstract.controller import SoftwareManager, SearchResult, UpgradeRequirements, TransactionResult, \ from bauh.api.abstract.controller import SoftwareManager, SearchResult, UpgradeRequirements, TransactionResult, \
@@ -74,13 +74,22 @@ class WebApplicationManager(SoftwareManager):
self.suggestions = {} self.suggestions = {}
self.configman = WebConfigManager() self.configman = WebConfigManager()
self.idxman = SearchIndexManager(logger=context.logger) self.idxman = SearchIndexManager(logger=context.logger)
self.custom_actions = [CustomSoftwareAction(i18n_label_key='web.custom_action.clean_env', self.custom_actions = [
i18n_status_key='web.custom_action.clean_env.status', CustomSoftwareAction(i18n_label_key='web.custom_action.install_app',
manager=self, i18n_status_key='web.custom_action.install_app.status',
manager_method='clean_environment', manager=self,
icon_path=resource.get_path('img/web.svg', ROOT_DIR), manager_method='install_app',
requires_root=False, icon_path=resource.get_path('img/web.svg', ROOT_DIR),
refresh=False)] requires_root=False,
refresh=True),
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)
]
def _get_lang_header(self) -> str: def _get_lang_header(self) -> str:
try: try:
@@ -222,7 +231,7 @@ class WebApplicationManager(SoftwareManager):
def _strip_url_protocol(self, url: str) -> str: def _strip_url_protocol(self, url: str) -> str:
return RE_PROTOCOL_STRIP.split(url)[1].strip().lower() 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) super(WebApplicationManager, self).serialize_to_disk(pkg=pkg, icon_bytes=None, only_icon=False)
def _request_url(self, url: str) -> Optional[Response]: def _request_url(self, url: str) -> Optional[Response]:
@@ -230,8 +239,8 @@ class WebApplicationManager(SoftwareManager):
try: try:
return self.http_client.get(url, headers=headers, ignore_ssl=True, single_call=True, session=False, allow_redirects=True) return self.http_client.get(url, headers=headers, ignore_ssl=True, single_call=True, session=False, allow_redirects=True)
except (exceptions.ConnectionError, requests.exceptions.TooManyRedirects) as e: except Exception as e:
self.logger.warning(f"Could not GET {url}. Exception: {e.__class__.__name__}") self.logger.warning(f"Could not GET '{url}'. Exception: {e.__class__.__name__}")
def _map_url(self, url: str) -> Tuple["BeautifulSoup", requests.Response]: def _map_url(self, url: str) -> Tuple["BeautifulSoup", requests.Response]:
url_res = self._request_url(url) url_res = self._request_url(url)
@@ -473,10 +482,10 @@ class WebApplicationManager(SoftwareManager):
def get_history(self, pkg: SoftwarePackage) -> PackageHistory: def get_history(self, pkg: SoftwarePackage) -> PackageHistory:
pass 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']) 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'], value=app.url, read_only=pre_validated)
inp_name = TextInputComponent(label=self.i18n['name'], value=app.name) inp_name = TextInputComponent(label=self.i18n['name'], value=app.name)
inp_desc = TextInputComponent(label=self.i18n['description'], value=app.description) inp_desc = TextInputComponent(label=self.i18n['description'], value=app.description)
@@ -513,11 +522,15 @@ class WebApplicationManager(SoftwareManager):
icon_op_ded = InputOption(id_='icon_ded', label=self.i18n['web.install.option.wicon.deducted.label'], value=0, 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')) 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, 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, 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']) label=self.i18n['web.install.option.wicon.label'])
@@ -544,13 +557,24 @@ class WebApplicationManager(SoftwareManager):
check_options = MultipleSelectComponent(options=adv_opts, default_options=def_adv_opts, label=self.i18n['web.install.options.advanced'].capitalize()) 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'], install_ = watcher.request_confirmation(title=self.i18n['web.install.options_dialog.title'],
body=None, body=None,
components=[form_1, check_options], components=[form_1, check_options],
confirmation_label=self.i18n['continue'].capitalize(), confirmation_label=self.i18n['continue'].capitalize(),
deny_label=self.i18n['cancel'].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=bold(f'"{inp_url.get_value()}"')))
return False, []
else:
app.url = typed_url
if res:
selected = [] selected = []
if check_options.values: if check_options.values:
@@ -579,9 +603,9 @@ class WebApplicationManager(SoftwareManager):
app.set_custom_icon(icon_chooser.file_path) app.set_custom_icon(icon_chooser.file_path)
selected.append('--icon={}'.format(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, [] return False, []
@@ -649,13 +673,9 @@ class WebApplicationManager(SoftwareManager):
pkg.name)) pkg.name))
traceback.print_exc() traceback.print_exc()
def install(self, pkg: WebApplication, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult: def _install(self, pkg: WebApplication, install_options: List[str], watcher: ProcessWatcher) -> TransactionResult:
continue_install, install_options = self._ask_install_options(pkg, watcher)
widevine_support = '--widevine' in install_options widevine_support = '--widevine' in install_options
if not continue_install:
return TransactionResult(success=False, installed=[], removed=[])
watcher.change_substatus(self.i18n['web.env.checking']) watcher.change_substatus(self.i18n['web.env.checking'])
handler = ProcessHandler(watcher) handler = ProcessHandler(watcher)
@@ -664,7 +684,8 @@ class WebApplicationManager(SoftwareManager):
if web_config['environment']['system'] and not nativefier.is_available(): if web_config['environment']['system'] and not nativefier.is_available():
watcher.show_message(title=self.i18n['error'].capitalize(), 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) type_=MessageType.ERROR)
return TransactionResult(success=False, installed=[], removed=[]) return TransactionResult(success=False, installed=[], removed=[])
@@ -677,8 +698,9 @@ class WebApplicationManager(SoftwareManager):
if comps_to_update and not self._ask_update_permission(comps_to_update, watcher): if comps_to_update and not self._ask_update_permission(comps_to_update, watcher):
return TransactionResult(success=False, installed=[], removed=[]) return TransactionResult(success=False, installed=[], removed=[])
if not self.env_updater.update(components=comps_to_update, handler=handler): 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) 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=[]) return TransactionResult(success=False, installed=[], removed=[])
Path(INSTALLED_PATH).mkdir(parents=True, exist_ok=True) Path(INSTALLED_PATH).mkdir(parents=True, exist_ok=True)
@@ -737,11 +759,13 @@ class WebApplicationManager(SoftwareManager):
if hasattr(pkg, prop): if hasattr(pkg, prop):
try: try:
setattr(pkg, prop, val) setattr(pkg, prop, val)
self.logger.info(f"Using custom installation property '{prop}' ({val if val else '<null>'}) for '{url_domain}' " self.logger.info(
f"(Electron: {electron_version})") f"Using custom installation property '{prop}' ({val if val else '<null>'}) for '{url_domain}' "
f"(Electron: {electron_version})")
except: except:
self.logger.error(f"Could not set the custom installation property '{prop}' ({val if val else '<null>'}) " self.logger.error(
f"for '{url_domain}' (Electron: {electron_version})") f"Could not set the custom installation property '{prop}' ({val if val else '<null>'}) "
f"for '{url_domain}' (Electron: {electron_version})")
watcher.change_substatus(self.i18n['web.install.substatus.call_nativefier'].format(bold('nativefier'))) watcher.change_substatus(self.i18n['web.install.substatus.call_nativefier'].format(bold('nativefier')))
@@ -754,7 +778,8 @@ class WebApplicationManager(SoftwareManager):
if not installed: if not installed:
msg = '{}.{}.'.format(self.i18n['wen.install.error'].format(bold(pkg.name)), 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) watcher.show_message(title=self.i18n['error'], body=msg, type_=MessageType.ERROR)
return TransactionResult(success=False, installed=[], removed=[]) return TransactionResult(success=False, installed=[], removed=[])
@@ -825,6 +850,27 @@ class WebApplicationManager(SoftwareManager):
return TransactionResult(success=True, installed=[pkg], removed=[]) 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: def _gen_desktop_entry_content(self, pkg: WebApplication) -> str:
return """ return """
[Desktop Entry] [Desktop Entry]

View File

@@ -4,6 +4,9 @@ web.custom_action.clean_env.failed=An error occurred during the installation env
web.custom_action.clean_env.status=Cleaning the installation environment web.custom_action.clean_env.status=Cleaning the installation environment
web.custom_action.clean_env.success=Installation environment cleaned web.custom_action.clean_env.success=Installation environment cleaned
web.custom_action.clean_env=Clean installation environment 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} 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.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 wil not be possible to install {}.
web.environment.install=Installing {} web.environment.install=Installing {}

View File

@@ -4,6 +4,9 @@ web.custom_action.clean_env.failed=An error occurred during the installation env
web.custom_action.clean_env.status=Cleaning the installation environment web.custom_action.clean_env.status=Cleaning the installation environment
web.custom_action.clean_env.success=Installation environment cleaned web.custom_action.clean_env.success=Installation environment cleaned
web.custom_action.clean_env=Clean installation environment 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} 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.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 wil not be possible to install {}.
web.environment.install=Installing {} web.environment.install=Installing {}

View File

@@ -4,6 +4,9 @@ web.custom_action.clean_env.failed=Se produjo un error durante la limpieza del a
web.custom_action.clean_env.status=Limpiando el 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.success=Ambiente de instalación limpio
web.custom_action.clean_env=Limpiar ambiente de instalación 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}. 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.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.env.error=Parece que hay problemas con el ambiente de instalación web. No será posible instalar {}.
web.environment.install=Instalando {} web.environment.install=Instalando {}

View File

@@ -4,6 +4,9 @@ web.custom_action.clean_env.failed=Une erreur est survenue lors du nettoyage de
web.custom_action.clean_env.status=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.success=Environnement d'installation nettoyé
web.custom_action.clean_env=Nettoyage de l'environnement d'installation Web. 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} 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.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.env.error=Il y a des problèmes avec l'environnement d'installation Web. {} ne pourra pas être installé.
web.environment.install=Intallation de {} web.environment.install=Intallation de {}

View File

@@ -4,6 +4,9 @@ web.custom_action.clean_env.failed=An error occurred during the installation env
web.custom_action.clean_env.status=Cleaning the installation environment web.custom_action.clean_env.status=Cleaning the installation environment
web.custom_action.clean_env.success=Installation environment cleaned web.custom_action.clean_env.success=Installation environment cleaned
web.custom_action.clean_env=Clean installation environment 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} 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.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 wil not be possible to install {}.
web.environment.install=Installing {} web.environment.install=Installing {}

View File

@@ -4,6 +4,9 @@ web.custom_action.clean_env.failed=Ocorreu um problema durante a limpeza do ambi
web.custom_action.clean_env.status=Limpando 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.success=Ambiente de instalação limpo
web.custom_action.clean_env=Limpar ambiente de instalação 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} 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.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.env.error=Parce que existem problemas com o ambiente de instalação Web. Não será possível instalar {}.
web.environment.nativefier=Instalando {} web.environment.nativefier=Instalando {}

View File

@@ -4,6 +4,9 @@ web.custom_action.clean_env=Очистка среды установки
web.custom_action.clean_env.failed=Произошла ошибка при очистке среды установки web.custom_action.clean_env.failed=Произошла ошибка при очистке среды установки
web.custom_action.clean_env.status=Среда установки будет очищена web.custom_action.clean_env.status=Среда установки будет очищена
web.custom_action.clean_env.success=Среда установки очищена! web.custom_action.clean_env.success=Среда установки очищена!
web.custom_action.install_app=Install Web application
web.custom_action.install_app.invalid_url=The address {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.checking=Проверка Веб-среды
web.env.error=Проблемы с Веб-средой. Невозможно установить {}. web.env.error=Проблемы с Веб-средой. Невозможно установить {}.
web.environment.install=Устанавливается {} web.environment.install=Устанавливается {}

View File

@@ -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.status=Kurulum ortamını temizleme
web.custom_action.clean_env.success=Kurulum ortamı temizlendi web.custom_action.clean_env.success=Kurulum ortamı temizlendi
web.custom_action.clean_env=Temiz kurulum ortamı 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} 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.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.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 web.environment.install={} yükleniyor