[wgem] adding category

This commit is contained in:
Vinícius Moreira
2019-12-13 16:35:00 -03:00
parent 7a3df32471
commit 174349cf5b
17 changed files with 156 additions and 59 deletions

View File

@@ -36,6 +36,8 @@ class ApplicationContext:
self.file_downloader = file_downloader
self.arch_x86_64 = sys.maxsize > 2**32
self.distro = distro
self.default_categories = ('AudioVideo', 'Audio', 'Video', 'Development', 'Education', 'Game',
'Graphics', 'Network', 'Office', 'Science', 'Settings', 'System', 'Utility')
def is_system_x86_64(self):
return self.arch_x86_64

View File

@@ -5,6 +5,8 @@ from abc import ABC, abstractmethod
from pathlib import Path
from typing import List, Set, Type
import yaml
from bauh.api.abstract.context import ApplicationContext
from bauh.api.abstract.disk import DiskCacheLoader
from bauh.api.abstract.handler import ProcessWatcher
@@ -178,8 +180,15 @@ class SoftwareManager(ABC):
data = pkg.get_data_to_cache()
if data:
with open(pkg.get_disk_data_path(), 'w+') as f:
f.write(json.dumps(data))
disk_path = pkg.get_disk_data_path()
ext = disk_path.split('.')[-1]
if ext == 'json':
with open(disk_path, 'w+') as f:
f.write(json.dumps(data))
elif ext in ('yml', 'yaml'):
with open(disk_path, 'w+') as f:
f.write(yaml.dump(data))
if icon_bytes:
Path(pkg.get_disk_cache_path()).mkdir(parents=True, exist_ok=True)

View File

@@ -67,7 +67,7 @@ class SingleSelectComponent(InputViewComponent):
self.value = default_option
self.max_per_line = max_per_line
def get_selected_value(self):
def get_selected(self):
if self.value:
return self.value.value
@@ -102,12 +102,13 @@ class TextComponent(ViewComponent):
class TextInputComponent(ViewComponent):
def __init__(self, label: str, value: str = '', placeholder: str = None, tooltip: str = None, id_: str = None):
def __init__(self, label: str, value: str = '', placeholder: str = None, tooltip: str = None, read_only: bool =False, id_: str = None):
super(TextInputComponent, self).__init__(id_=id_)
self.label = label
self.value = value
self.tooltip = tooltip
self.placeholder = placeholder
self.read_only = read_only
def get_value(self) -> str:
if self.value is not None:

View File

@@ -16,7 +16,7 @@ class HttpClient:
self.sleep = sleep
self.logger = logger
def get(self, url: str, params: dict = None, headers: dict = None, allow_redirects: bool = True, ignore_ssl: bool = False):
def get(self, url: str, params: dict = None, headers: dict = None, allow_redirects: bool = True, ignore_ssl: bool = False, single_call: bool = False):
cur_attempts = 1
while cur_attempts <= self.max_attempts:
@@ -39,6 +39,9 @@ class HttpClient:
if res.status_code == 200:
return res
if single_call:
return
if self.sleep > 0:
time.sleep(self.sleep)
except Exception as e:

View File

@@ -1,5 +1,4 @@
import glob
import json
import locale
import os
import re
@@ -11,6 +10,8 @@ from pathlib import Path
from threading import Thread
from typing import List, Type, Set, Tuple
import yaml
from bauh.api.abstract.context import ApplicationContext
from bauh.api.abstract.controller import SoftwareManager, SearchResult
from bauh.api.abstract.disk import DiskCacheLoader
@@ -39,6 +40,7 @@ except:
RE_PROTOCOL_STRIP = re.compile(r'[a-zA-Z]+://')
RE_SEVERAL_SPACES = re.compile(r'\s+')
RE_SYMBOLS_SPLIT = re.compile(r'[\-|_\s]')
class WebApplicationManager(SoftwareManager):
@@ -70,13 +72,10 @@ class WebApplicationManager(SoftwareManager):
name = name_tag.text.strip() if name_tag else None
if not name:
name = url_no_protocol.split('.')[0]
name = url_no_protocol.split('.')[0].strip()
if name:
name = name.replace('|', '').replace('_', ' ')
if ' ' in name:
name = RE_SEVERAL_SPACES.sub(' ', name)
name = RE_SYMBOLS_SPLIT.split(name)[0].strip()
return name
@@ -115,7 +114,7 @@ class WebApplicationManager(SoftwareManager):
if installed_matches:
res.installed.extend(installed_matches)
else:
url_res = self.http_client.get(url, headers={'Accept-language': self._get_lang_header()}, ignore_ssl=True)
url_res = self.http_client.get(url, headers={'Accept-language': self._get_lang_header()}, ignore_ssl=True, single_call=True)
if url_res:
soup = BeautifulSoup(url_res.text, 'lxml', parse_only=SoupStrainer('head'))
@@ -149,9 +148,9 @@ class WebApplicationManager(SoftwareManager):
res = SearchResult([], [], 0)
if os.path.exists(INSTALLED_PATH):
for data_path in glob.glob('{}/*/*data.json'.format(INSTALLED_PATH)):
for data_path in glob.glob('{}/*/*data.yml'.format(INSTALLED_PATH)):
with open(data_path, 'r') as f:
res.installed.append(WebApplication(installed=True, **json.loads(f.read())))
res.installed.append(WebApplication(installed=True, **yaml.safe_load(f.read())))
res.total += 1
return res
@@ -199,12 +198,15 @@ class WebApplicationManager(SoftwareManager):
def get_info(self, pkg: WebApplication) -> dict:
if pkg.installed:
info = {'{}_{}'.format(idx + 1, att): getattr(pkg, att) for idx, att in enumerate(('url', 'description', 'version', 'installation_dir', 'desktop_entry'))}
info['6_exec_file'] = pkg.get_exec_path()
info['7_icon_path'] = pkg.get_disk_icon_path()
info = {'{}_{}'.format(idx + 1, att): getattr(pkg, att) for idx, att in enumerate(('url', 'description', 'version', 'categories', 'installation_dir', 'desktop_entry'))}
info['7_exec_file'] = pkg.get_exec_path()
info['8_icon_path'] = pkg.get_disk_icon_path()
if os.path.exists(pkg.installation_dir):
info['8_size'] = get_human_size_str(get_dir_size(pkg.installation_dir))
info['9_size'] = get_human_size_str(get_dir_size(pkg.installation_dir))
if info.get('4_categories'):
info['4_categories'] = [self.i18n[c.lower()].capitalize() for c in info['4_categories']]
return info
@@ -214,9 +216,15 @@ class WebApplicationManager(SoftwareManager):
def _ask_install_options(self, app: WebApplication, watcher: ProcessWatcher) -> 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_name = TextInputComponent(label=self.i18n['name'], value=app.name)
inp_desc = TextInputComponent(label=self.i18n['description'], value=app.description)
cat_ops = [InputOption(label=self.i18n['web.install.option.category.none'].capitalize(), value=0)]
cat_ops.extend([InputOption(label=self.i18n[c.lower()].capitalize(), value=c) for c in self.context.default_categories])
inp_cat = SingleSelectComponent(label=self.i18n['category'], type_=SelectViewType.COMBO, options=cat_ops, default_option=cat_ops[0])
tray_op_off = InputOption(label=self.i18n['web.install.option.tray.off.label'], value=0, tooltip=self.i18n['web.install.option.tray.off.tip'])
tray_op_default = InputOption(label=self.i18n['web.install.option.tray.default.label'], value='--tray', tooltip=self.i18n['web.install.option.tray.default.tip'])
tray_op_min = InputOption(label=self.i18n['web.install.option.tray.min.label'], value='--tray=start-in-tray', tooltip=self.i18n['web.install.option.tray.min.tip'])
@@ -225,7 +233,7 @@ class WebApplicationManager(SoftwareManager):
options=[tray_op_off, tray_op_default, tray_op_min],
label=self.i18n['web.install.option.tray.label'])
form_1 = FormComponent(components=[inp_name, inp_desc, inp_tray])
form_1 = FormComponent(components=[inp_url, inp_name, inp_desc, inp_cat, inp_tray])
op_single = InputOption(id_='single', label=self.i18n['web.install.option.single.label'], value="--single-instance", tooltip=self.i18n['web.install.option.single.tip'])
op_max = InputOption(id_='max', label=self.i18n['web.install.option.max.label'], value="--maximize", tooltip=self.i18n['web.install.option.max.tip'])
@@ -238,11 +246,10 @@ class WebApplicationManager(SoftwareManager):
check_options = MultipleSelectComponent(options=[op_single, op_allow_urls, op_max, op_fs, op_nframe, op_ncache, op_insecure, op_igcert], default_options={op_single}, label='')
bt_continue = self.i18n['continue'].capitalize()
res = watcher.request_confirmation(title=self.i18n['web.install.options_dialog.title'],
body=self.i18n['web.install.options_dialog.body'].format(bold(bt_continue)),
body=None,
components=[form_1, check_options],
confirmation_label=bt_continue,
confirmation_label=self.i18n['continue'].capitalize(),
deny_label=self.i18n['cancel'].capitalize())
if res:
@@ -251,7 +258,7 @@ class WebApplicationManager(SoftwareManager):
if check_options.values:
selected.extend(check_options.get_selected_values())
tray_mode = inp_tray.get_selected_value()
tray_mode = inp_tray.get_selected()
if tray_mode is not None and tray_mode != 0:
selected.append(tray_mode)
@@ -265,6 +272,11 @@ class WebApplicationManager(SoftwareManager):
if custom_desc:
app.description = inp_desc.get_value()
cat = inp_cat.get_selected()
if cat != 0:
app.categories = [cat]
return res, selected
return False, []
@@ -385,12 +397,13 @@ class WebApplicationManager(SoftwareManager):
[Desktop Entry]
Type=Application
Name={name} ( web )
Categories=Applications;
Comment={desc}
Icon={icon}
Exec={exec_path}
{categories}
""".format(name=pkg.name, exec_path=pkg.get_exec_path(),
desc=pkg.description or pkg.url, icon=pkg.get_disk_icon_path())
desc=pkg.description or pkg.url, icon=pkg.get_disk_icon_path(),
categories='Categories={}'.format(';'.join(pkg.categories)) if pkg.categories else '')
def is_enabled(self) -> bool:
return self.enabled

View File

@@ -1,4 +1,5 @@
import re
from typing import List
from bauh.api.abstract.model import SoftwarePackage
from bauh.commons import resource
@@ -8,9 +9,11 @@ from bauh.gems.web import ROOT_DIR
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):
installation_dir: str = None, desktop_entry: str = None, installed: bool = False, version: str = None,
categories: List[str] = None):
super(WebApplication, self).__init__(id=id if id else url, name=name, description=description,
icon_url=icon_url, installed=installed, version=version)
icon_url=icon_url, installed=installed, version=version,
categories=categories)
self.url = url
self.installation_dir = installation_dir
self.desktop_entry = desktop_entry
@@ -23,7 +26,7 @@ class WebApplication(SoftwarePackage):
@staticmethod
def _get_cached_attrs() -> tuple:
return 'id', 'name', 'version', 'url', 'description', 'icon_url', 'installation_dir', 'desktop_entry'
return 'id', 'name', 'version', 'url', 'description', 'icon_url', 'installation_dir', 'desktop_entry', 'categories'
def can_be_downgraded(self):
return False
@@ -41,6 +44,9 @@ class WebApplication(SoftwarePackage):
def get_default_icon_path(self) -> str:
return resource.get_path('img/web.png', ROOT_DIR)
def get_disk_data_path(self):
return '{}/data.yml'.format(self.get_disk_cache_path())
def is_application(self):
return True

View File

@@ -1,10 +1,9 @@
gem.web.info=It allows to install Web applications on your system
gem.web.info=It allows to install Web applications on the system through they addresses ( URLs )
web.environment.install=Installing {}
web.waiting.env_updater=Updating 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.install.options_dialog.title=Installation options
web.install.options_dialog.body=Select the wanted installation options and click in the {} button
web.install.error=An error has happened during the {} installation
web.install.nativefier.error.unknown=Have a look at the {} to identify the reason
web.install.nativefier.error.inner_dir=The app installation directory was not found in {}
@@ -35,13 +34,15 @@ web.install.option.tray.default.label=Default
web.install.option.tray.default.tip=The app icon will be attached to the system tray allowing it to be quickly opened
web.install.option.tray.min.label=Start minimized
web.install.option.tray.min.tip=The app will be started minimized as an icon in the system tray
web.install.option.category.none=none
web.uninstall.error.install_dir.not_found=The installation directory {} was not found
web.uninstall.error.remove_dir=It was not possible to remove {}
web.info.1_url=URL
web.info.2_description=description
web.info.3_version=version
web.info.4_installation_dir=installation dir
web.info.5_desktop_entry=shortcut
web.info.6_exec_file=executable
web.info.7_icon_path=icon
web.info.8_size=size
web.info.4_categories=categories
web.info.5_installation_dir=installation dir
web.info.6_desktop_entry=shortcut
web.info.7_exec_file=executable
web.info.8_icon_path=icon
web.info.9_size=size

View File

@@ -1,10 +1,9 @@
gem.web.info=Permite instalar aplicações Web no seu sistema
gem.web.info=Permite instalar aplicações Web no seu sistema através dos seus endereços ( URLs )
web.environment.nativefier=Instalando {}
web.waiting.env_updater=Atualizando ambiente
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.install.options_dialog.title=Opções de instalação
web.install.options_dialog.body=Selecione as opções de instalação desejadas e clique no botão {}
wen.install.error=Ocorreu um erro durante a instalação de {}
web.install.nativefier.error.unknown=Dê uma olhada em {} para identificar o motivo
web.install.nativefier.error.inner_dir=O diretório de instalação do aplicativo não foi encontrado em {}
@@ -35,13 +34,15 @@ web.install.option.tray.default.label=Padrão
web.install.option.tray.default.tip=O ícone do aplicativo será anexado a bandeja do sistema permitindo que o mesma seja rapidamente aberto
web.install.option.tray.min.label=Iniciar minimizado
web.install.option.tray.min.tip=O aplicativo será iniciado minimizado como um ícone da bandeja do sistema
web.install.option.category.none=nenhuma
web.uninstall.error.install_dir.not_found=O diretório de instalação {} não foi encontrado
web.uninstall.error.remove=Não foi possível remover {}
web.info.1_url=URL
web.info.2_description=descrição
web.info.3_version=versão
web.info.4_installation_dir=diretório de instalação
web.info.5_desktop_entry=atalho
web.info.6_exec_file=executável
web.info.7_icon_path=ícone
web.info.8_size=tamanho
web.info.4_categories=categorias
web.info.5_installation_dir=diretório de instalação
web.info.6_desktop_entry=atalho
web.info.7_exec_file=executável
web.info.8_icon_path=ícone
web.info.9_size=tamanho

View File

@@ -277,6 +277,9 @@ class FormQt(QGroupBox):
line_edit.setText(c.value)
line_edit.setCursorPosition(0)
if c.read_only:
line_edit.setEnabled(False)
def update_model(text: str):
c.value = text

View File

@@ -1,12 +1,12 @@
from typing import List
from PyQt5.QtCore import QSize
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtWidgets import QMessageBox, QVBoxLayout, QLabel, QWidget, QScrollArea, QFrame
from bauh.api.abstract.view import ViewComponent, SingleSelectComponent, MultipleSelectComponent, TextInputComponent, \
FormComponent
from bauh.view.qt import css
from bauh.view.qt.components import MultipleSelectQt, new_single_select, TextInputQt, FormQt
from bauh.view.qt.components import MultipleSelectQt, new_single_select, TextInputQt, FormQt, new_spacer
from bauh.view.util.translation import I18n
@@ -28,6 +28,7 @@ class ConfirmationDialog(QMessageBox):
self.layout().addWidget(QLabel(body), 0, 1)
width = 0
if components:
scroll = QScrollArea(self)
scroll.setFrameShape(QFrame.NoFrame)
@@ -52,13 +53,21 @@ class ConfirmationDialog(QMessageBox):
raise Exception("Cannot render instances of " + comp.__class__.__name__)
height += inst.sizeHint().height()
if inst.sizeHint().width() > width:
width = inst.sizeHint().width()
comps_container.layout().addWidget(inst)
height = height if height < int(screen_size.height() / 2.5) else int(screen_size.height() / 2.5)
scroll.setFixedHeight(height)
self.layout().addWidget(scroll, 1, 1)
if not body and width > 0:
self.layout().addWidget(QLabel(' ' * int(width / 2)), 0, 1)
self.exec_()
def is_confirmed(self):

View File

@@ -174,12 +174,14 @@ texteditor=editor de text
server=servidor
cloud=núvol
instantmessaging=missatgeria
messaging=missatgeria
2dgraphics=gràfics 2d
vectorgraphics=gràfics vectorials
office=oficina
devices=dispositius
security=seguretat
audiovideo=àudio/vídeo
audio=àudio
amusement=diversió
webdevelopment=desenvolupament web
filetransfer=transferència de fitxers
@@ -202,4 +204,8 @@ mirror=mirall
emulator=emulador
do_not.install=no instal·leu
repository=dipòsit
details=detalls
details=detalls
communication=comunicació
administration=administració
settings=configuració
address=adreça

View File

@@ -137,9 +137,11 @@ texteditor=Texteditor
screenshots.bt_next.label=Weiter
screenshots.bt_back.label=Zurück
instantmessaging=Kommunikation
messaging=messaging
2dgraphics=2d Grafik
vectorgraphics=Vectorgrafik
audiovideo=Audio / Video
audio=audio
webdevelopment=Webentwicklung
filetransfer=Dateiübertragung
packagemanager=Paketmanager
@@ -156,4 +158,8 @@ publisher.verified=Verifiziert
emulator=emulator
do_not.install=nicht installieren
repository=repository
details=details
details=details
communication=Kommunikation
administration=Verwaltung
settings=Einstellungen
address=Adresse

View File

@@ -157,4 +157,12 @@ mirror=mirror
emulator=emulator
do_not.install=don't install
repository=repository
details=details
details=details
communication=communication
web=web
office=office
messaging=messaging
administration=administration
audio=audio
settings=settings
address=address

View File

@@ -174,12 +174,14 @@ texteditor=editor de texto
server=servidor
cloud=nube
instantmessaging=mensajería
messaging=mensajería
2dgraphics=gráficos 2d
vectorgraphics=gráficos vectoriales
office=oficina
devices=dispositivos
security=seguridad
audiovideo=audio y vídeo
audio=audio
amusement=diversión
webdevelopment=desarrollo web
filetransfer=transferencia de archivos
@@ -201,4 +203,8 @@ mirror=espejo
emulator=emulador
do_not.install=no instalar
repository=repositorio
details=detalles
details=detalles
communication=comunicación
administration=administración
settings=configuraciones
address=dirección

View File

@@ -137,9 +137,11 @@ texteditor=editor di testo
screenshots.bt_next.label=prossimo
screenshots.bt_back.label=precedente
instantmessaging=messaggistica
messaging=messaggistica
2dgraphics=Grafica 2D
vectorgraphics=grafica vettoriale
audiovideo=audio / video
audio=audio
webdevelopment=sviluppo web
filetransfer=trasferimento di file
packagemanager=gestore pacchetti
@@ -157,4 +159,8 @@ mirror=specchio
emulator=emulatore
do_not.install=non installare
repository=deposito
details=dettagli
details=dettagli
communication=comunicazione
administration=amministrazione
settings=impostazioni
address=indirizzo

View File

@@ -172,16 +172,18 @@ news=notícias
weather=tempo
finance=finanças
desktopsettings=configurações
settings=configurações
texteditor=editor de texto
server=servidor
cloud=nuvem
instantmessaging=mensagens
instantmessaging=mensagem
2dgraphics=gŕaficos 2d
vectorgraphics=gráficos vetoriais
office=escritório
devices=dispositivos
security=segurança
audiovideo=áudio / vídeo
audio=áudio
amusement=diversão
webdevelopment=desenvolvimento web
filetransfer=transferência de arquivos
@@ -204,4 +206,8 @@ mirror=espelho
emulator=emulador
do_not.install=não instalar
repository=repositório
details=detalhes
details=detalhes
communication=comunicação
messaging=mensagem
administration=administração
address=endereço

View File

@@ -5,6 +5,8 @@ import time
from threading import Thread, Lock
from typing import Type, Dict
import yaml
from bauh.api.abstract.cache import MemoryCache
from bauh.api.abstract.disk import DiskCacheLoader, DiskCacheLoaderFactory
from bauh.api.abstract.model import SoftwarePackage
@@ -52,16 +54,25 @@ class AsyncDiskCacheLoader(Thread, DiskCacheLoader):
def _fill_cached_data(self, pkg: SoftwarePackage) -> bool:
if self.enabled:
if os.path.exists(pkg.get_disk_data_path()):
with open(pkg.get_disk_data_path()) as f:
cached_data = json.loads(f.read())
if cached_data:
pkg.fill_cached_data(cached_data)
cache = self.cache_map.get(pkg.__class__)\
disk_path = pkg.get_disk_data_path()
ext = disk_path.split('.')[-1]
if cache:
cache.add_non_existing(pkg.id, cached_data)
with open(disk_path) as f:
if ext == 'json':
cached_data = json.loads(f.read())
elif ext in {'yml', 'yaml'}:
cached_data = yaml.load(f.read())
else:
raise Exception('The cached data file {} has an unsupported format'.format(disk_path))
return True
if cached_data:
pkg.fill_cached_data(cached_data)
cache = self.cache_map.get(pkg.__class__)
if cache:
cache.add_non_existing(pkg.id, cached_data)
return True
return False