[web] installing, removing and reading

This commit is contained in:
Vinícius Moreira
2019-12-11 18:49:43 -03:00
parent 49a3f12047
commit 3e46822e5e
15 changed files with 261 additions and 62 deletions

View File

@@ -3,3 +3,4 @@ from pathlib import Path
HOME_PATH = Path.home() HOME_PATH = Path.home()
CACHE_PATH = '{}/.cache/bauh'.format(HOME_PATH) CACHE_PATH = '{}/.cache/bauh'.format(HOME_PATH)
CONFIG_PATH = '{}/.config/bauh'.format(HOME_PATH) CONFIG_PATH = '{}/.config/bauh'.format(HOME_PATH)
DESKTOP_ENTRIES_DIR = '{}/.local/share/applications'.format(HOME_PATH)

View File

@@ -1,9 +1,10 @@
import os import os
from bauh.api.constants import HOME_PATH from bauh.api.constants import HOME_PATH, DESKTOP_ENTRIES_DIR
ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
BIN_PATH = '{}/.local/share/bauh/web'.format(HOME_PATH) BIN_PATH = '{}/.local/share/bauh/web'.format(HOME_PATH)
INSTALLED_PATH = '{}/installed'.format(BIN_PATH)
NODE_DIR_PATH = '{}/node'.format(BIN_PATH) NODE_DIR_PATH = '{}/node'.format(BIN_PATH)
NODE_BIN_PATH = '{}/bin/node'.format(NODE_DIR_PATH) NODE_BIN_PATH = '{}/bin/node'.format(NODE_DIR_PATH)
NPM_BIN_PATH = '{}/bin/npm'.format(NODE_DIR_PATH) NPM_BIN_PATH = '{}/bin/npm'.format(NODE_DIR_PATH)
@@ -13,3 +14,4 @@ ELECTRON_PATH = '{}/.cache/electron'.format(HOME_PATH)
ELECTRON_DOWNLOAD_URL = 'https://github.com/electron/electron/releases/download/v{version}/electron-v{version}-linux-{arch}.zip' 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_SHA256_URL = 'https://github.com/electron/electron/releases/download/v{version}/SHASUMS256.txt'
URL_ENVIRONMENT_SETTINGS = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/web/environment.yml' URL_ENVIRONMENT_SETTINGS = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/web/environment.yml'
DESKTOP_ENTRY_PATH_PATTERN = DESKTOP_ENTRIES_DIR + '/bauh.web.{name}.desktop'

View File

@@ -1,4 +1,10 @@
import glob
import json
import os import os
import shutil
import subprocess
import traceback
from pathlib import Path
from threading import Thread from threading import Thread
from typing import List, Type, Set from typing import List, Type, Set
@@ -8,8 +14,10 @@ from bauh.api.abstract.disk import DiskCacheLoader
from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.handler import ProcessWatcher
from bauh.api.abstract.model import SoftwarePackage, PackageAction, PackageSuggestion, PackageUpdate, PackageHistory from bauh.api.abstract.model import SoftwarePackage, PackageAction, PackageSuggestion, PackageUpdate, PackageHistory
from bauh.api.abstract.view import MessageType from bauh.api.abstract.view import MessageType
from bauh.commons import system
from bauh.commons.html import bold from bauh.commons.html import bold
from bauh.commons.system import ProcessHandler from bauh.commons.system import ProcessHandler
from bauh.gems.web import INSTALLED_PATH, nativefier, DESKTOP_ENTRY_PATH_PATTERN
from bauh.gems.web.environment import EnvironmentUpdater from bauh.gems.web.environment import EnvironmentUpdater
from bauh.gems.web.model import WebApplication from bauh.gems.web.model import WebApplication
@@ -37,6 +45,8 @@ class WebApplicationManager(SoftwareManager):
self.enabled = True self.enabled = True
self.i18n = context.i18n self.i18n = context.i18n
self.env_updater = env_updater self.env_updater = env_updater
self.env_settings = {}
self.logger = context.logger
def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult: def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult:
res = SearchResult([], [], 0) res = SearchResult([], [], 0)
@@ -56,7 +66,13 @@ class WebApplicationManager(SoftwareManager):
icon_tag = soup.head.find('link', attrs={"rel": "icon"}) icon_tag = soup.head.find('link', attrs={"rel": "icon"})
icon_url = icon_tag.get('href') if icon_tag else None icon_url = icon_tag.get('href') if icon_tag else None
res.new = [WebApplication(url=words, name=name, description=desc, icon_url=icon_url)] app = WebApplication(url=words, name=name, description=desc, icon_url=icon_url)
if self.env_settings.get('electron') and self.env_settings['electron'].get('version'):
app.version = self.env_settings['electron']['version']
app.latest_version = app.version
res.new = [app]
res.total = 1 res.total = 1
else: else:
# TODO # TODO
@@ -65,8 +81,15 @@ class WebApplicationManager(SoftwareManager):
return res return res
def read_installed(self, disk_loader: DiskCacheLoader, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = True) -> SearchResult: def read_installed(self, disk_loader: DiskCacheLoader, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = True) -> SearchResult:
# TODO res = SearchResult([], [], 0)
return SearchResult([], [], 0)
if os.path.exists(INSTALLED_PATH):
for data_path in glob.glob('{}/*/*data.json'.format(INSTALLED_PATH)):
with open(data_path, 'r') as f:
res.installed.append(WebApplication(installed=True, **json.loads(f.read())))
res.total += 1
return res
def downgrade(self, pkg: SoftwarePackage, root_password: str, handler: ProcessWatcher) -> bool: def downgrade(self, pkg: SoftwarePackage, root_password: str, handler: ProcessWatcher) -> bool:
pass pass
@@ -74,14 +97,47 @@ class WebApplicationManager(SoftwareManager):
def update(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool: def update(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool:
pass pass
def uninstall(self, pkg: SoftwarePackage, root_password: str, watcher: ProcessWatcher) -> bool: def uninstall(self, pkg: WebApplication, root_password: str, watcher: ProcessWatcher) -> bool:
pass self.logger.info("Checking if {} installation directory {} exists".format(pkg.name, pkg.installation_dir))
if not os.path.exists(pkg.installation_dir):
watcher.show_message(title=self.i18n['error'],
body=self.i18n['web.uninstall.error.install_dir.not_found'].format(bold(pkg.installation_dir)),
type_=MessageType.ERROR)
return False
self.logger.info("Removing {} installation directory {}".format(pkg.name, pkg.installation_dir))
try:
shutil.rmtree(pkg.installation_dir)
except:
watcher.show_message(title=self.i18n['error'],
body=self.i18n['web.uninstall.error.remove'].format(bold(pkg.installation_dir)),
type_=MessageType.ERROR)
traceback.print_exc()
return False
self.logger.info("Checking if {} desktop entry file {} exists".format(pkg.name, pkg.desktop_entry))
if os.path.exists(pkg.desktop_entry):
try:
os.remove(pkg.desktop_entry)
except:
watcher.show_message(title=self.i18n['error'],
body=self.i18n['web.uninstall.error.remove'].format(bold(pkg.desktop_entry)),
type_=MessageType.ERROR)
traceback.print_exc()
return False
return True
def get_managed_types(self) -> Set[Type[SoftwarePackage]]: def get_managed_types(self) -> Set[Type[SoftwarePackage]]:
return {WebApplication} return {WebApplication}
def get_info(self, pkg: SoftwarePackage) -> dict: def get_info(self, pkg: WebApplication) -> dict:
pass 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()
return info
def get_history(self, pkg: SoftwarePackage) -> PackageHistory: def get_history(self, pkg: SoftwarePackage) -> PackageHistory:
pass pass
@@ -97,8 +153,78 @@ class WebApplicationManager(SoftwareManager):
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 False return False
Path(INSTALLED_PATH).mkdir(parents=True, exist_ok=True)
pkg_name = pkg.name.lower()
app_name_id, app_dir = pkg_name, '{}/{}'.format(INSTALLED_PATH, pkg_name)
counter = 1
while True:
if not os.path.exists(app_dir):
break
else:
app_name_id += str(counter)
app_dir = '{}/{}'.format(INSTALLED_PATH, app_name_id)
counter += 1
installed = handler.handle_simple(nativefier.install(url=pkg.url, name=pkg_name, output_dir=app_dir,
electron_version=self.env_settings['electron']['version'], cwd=INSTALLED_PATH))
if not installed:
msg = '{}.{}.'.format(self.i18n['wen.install.error'].format(bold(pkg.name)),
self.i18n['web.install.nativefier.error.unknown'].format(bold(self.i18n['details'].capitalize())))
watcher.show_message(title=self.i18n['error'], body=msg, type_=MessageType.ERROR)
return False
inner_dir = os.listdir(app_dir)
if not inner_dir:
msg = '{}.{}.'.format(self.i18n['wen.install.error'].format(bold(pkg.name)),
self.i18n['web.install.nativefier.error.inner_dir'].format(bold(app_dir)))
watcher.show_message(title=self.i18n['error'], body=msg, type_=MessageType.ERROR)
return False
# bringing the inner app folder to the 'installed' folder level:
inner_dir = '{}/{}'.format(app_dir, inner_dir[0])
temp_dir = '{}/tmp_{}'.format(INSTALLED_PATH, app_name_id)
os.rename(inner_dir, temp_dir)
shutil.rmtree(app_dir)
os.rename(temp_dir, app_dir)
pkg.installation_dir = app_dir
version_path = '{}/version'.format(app_dir)
if os.path.exists(version_path):
with open(version_path, 'r') as f:
pkg.version = f.read().strip()
pkg.latest_version = pkg.version
desktop_entry = DESKTOP_ENTRY_PATH_PATTERN.format(name=app_name_id)
entry_content = self._gen_desktop_entry_content(pkg)
# TODO check if the desktop entry exists
with open(desktop_entry, 'w+') as f:
f.write(entry_content)
pkg.desktop_entry = desktop_entry
return True return True
def _gen_desktop_entry_content(self, pkg: WebApplication) -> str:
return """
[Desktop Entry]
Type=Application
Name={name} ( web )
Categories=Applications;
Comment={desc}
Icon={icon}
Exec={exec_path}
""".format(name=pkg.name, exec_path=pkg.get_exec_path(),
desc=pkg.description or pkg.url, icon=pkg.get_disk_icon_path())
def is_enabled(self) -> bool: def is_enabled(self) -> bool:
return self.enabled return self.enabled
@@ -112,7 +238,13 @@ class WebApplicationManager(SoftwareManager):
return False return False
def _update_environment(self, handler: ProcessHandler = None) -> bool: def _update_environment(self, handler: ProcessHandler = None) -> bool:
return self.node_updater.update_environment(self.context.is_system_x86_64(), handler=handler) updated_settings = self.node_updater.update_environment(self.context.is_system_x86_64(), handler=handler)
if updated_settings is not None:
self.env_settings = updated_settings
return True
return False
def prepare(self): def prepare(self):
if bool(int(os.getenv('BAUH_WEB_UPDATE_NODE', 1))): if bool(int(os.getenv('BAUH_WEB_UPDATE_NODE', 1))):
@@ -134,8 +266,8 @@ class WebApplicationManager(SoftwareManager):
def is_default_enabled(self) -> bool: def is_default_enabled(self) -> bool:
return True return True
def launch(self, pkg: SoftwarePackage): def launch(self, pkg: WebApplication):
pass subprocess.Popen(pkg.get_exec_path())
def get_screenshots(self, pkg: SoftwarePackage) -> List[str]: def get_screenshots(self, pkg: SoftwarePackage) -> List[str]:
pass pass

View File

@@ -163,12 +163,11 @@ class EnvironmentUpdater:
if self._download_electron(version=version, is_x86_x64_arch=is_x86_x64_arch, watcher=watcher): if self._download_electron(version=version, is_x86_x64_arch=is_x86_x64_arch, watcher=watcher):
return self._download_electron_sha256(version=version, watcher=watcher) return self._download_electron_sha256(version=version, watcher=watcher)
else: else:
files = run_cmd('ls', print_error=False, cwd=ELECTRON_PATH) files = os.listdir(ELECTRON_PATH)
if files: if files:
file_list = files.split('\n')
file_name = ELECTRON_DOWNLOAD_URL.format(version=version, arch='x64' if is_x86_x64_arch else 'ia32').split('/')[-1] file_name = ELECTRON_DOWNLOAD_URL.format(version=version, arch='x64' if is_x86_x64_arch else 'ia32').split('/')[-1]
electron_file = [f for f in file_list if f == file_name] electron_file = [f for f in files if f == file_name]
if electron_file: if electron_file:
self.logger.info("Electron {} already downloaded".format(version)) self.logger.info("Electron {} already downloaded".format(version))
@@ -177,7 +176,7 @@ class EnvironmentUpdater:
return False return False
file_name = ELECTRON_SHA256_URL.split('/')[-1] file_name = ELECTRON_SHA256_URL.split('/')[-1]
sha256_file = [f for f in file_list if f == file_name] sha256_file = [f for f in files if f == file_name]
if sha256_file: if sha256_file:
self.logger.info("Electron {} sha256 already downloaded".format(version)) self.logger.info("Electron {} sha256 already downloaded".format(version))
@@ -191,37 +190,44 @@ class EnvironmentUpdater:
return False return False
def update_environment(self, is_x86_x64_arch: bool, handler: ProcessHandler = None) -> bool: def get_environment(self) -> dict:
try: try:
res = self.http_client.get(URL_ENVIRONMENT_SETTINGS) res = self.http_client.get(URL_ENVIRONMENT_SETTINGS)
if not res: if not res:
self.logger.warning('Could not retrieve the environments settings from the cloud') self.logger.warning('Could not retrieve the environments settings from the cloud')
return False return
try: try:
settings = yaml.safe_load(res.content) return yaml.safe_load(res.content)
except yaml.YAMLError: except yaml.YAMLError:
self.logger.error('Could not parse environment settings: {}'.format(res.text)) self.logger.error('Could not parse environment settings: {}'.format(res.text))
return False return
if not self.update_node(version=settings['nodejs']['version'], version_url=settings['nodejs']['url'],
watcher=handler.watcher if handler else None):
self.logger.warning('Could not install / update NodeJS')
return False
if not self.install_nativefier(version=settings['nativefier']['version'], handler=handler):
self.logger.warning('Could not install / update nativefier')
return False
res = self.install_electron(version=settings['electron']['version'], is_x86_x64_arch=is_x86_x64_arch,
watcher=handler.watcher if handler else None)
if res:
self.logger.info('Environment updated')
else:
self.logger.warning('Could not update the environment')
return res
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
return False return
def update_environment(self, is_x86_x64_arch: bool, handler: ProcessHandler = None) -> dict:
settings = self.get_environment()
if settings is None:
return
if not self.update_node(version=settings['nodejs']['version'], version_url=settings['nodejs']['url'],
watcher=handler.watcher if handler else None):
self.logger.warning('Could not install / update NodeJS')
return
if not self.install_nativefier(version=settings['nativefier']['version'], handler=handler):
self.logger.warning('Could not install / update nativefier')
return
res = self.install_electron(version=settings['electron']['version'], is_x86_x64_arch=is_x86_x64_arch,
watcher=handler.watcher if handler else None)
if res:
self.logger.info('Environment updated')
else:
self.logger.warning('Could not update the environment')
return settings

View File

@@ -6,19 +6,31 @@ from bauh.gems.web import ROOT_DIR
class WebApplication(SoftwarePackage): class WebApplication(SoftwarePackage):
def __init__(self, url: str, name: str, description: str, icon_url: str): def __init__(self, url: str = None, name: str = None, description: str = None, icon_url: str = None,
super(WebApplication, self).__init__(id=url, name=name, description=description, icon_url=icon_url) installation_dir: str = None, desktop_entry: str = None, installed: bool = False, version: str = None):
super(WebApplication, self).__init__(id=url, name=name, description=description,
icon_url=icon_url, installed=installed, version=version)
self.url = url self.url = url
self.installation_dir = installation_dir
self.desktop_entry = desktop_entry
def has_history(self): def has_history(self):
return False return False
def has_info(self): def has_info(self):
return False return self.installed
@staticmethod
def _get_cached_attrs() -> tuple:
return 'name', 'version', 'url', 'description', 'icon_url', 'installation_dir', 'desktop_entry'
def can_be_downgraded(self): def can_be_downgraded(self):
return False return False
def get_exec_path(self) -> str:
if self.installation_dir:
return '{}/{}'.format(self.installation_dir, self.installation_dir.split('/')[-1])
def get_type(self): def get_type(self):
return 'web' return 'web'
@@ -32,22 +44,32 @@ class WebApplication(SoftwarePackage):
return True return True
def supports_disk_cache(self): def supports_disk_cache(self):
return self.installed and self.is_application() return self.installed
def get_disk_cache_path(self): def get_disk_cache_path(self):
return CACHE_PATH + '/' + self.get_type() return self.installation_dir
def get_data_to_cache(self) -> dict: def get_data_to_cache(self) -> dict:
""" data = {}
:return: the application data that should be cached in disk / memory for quick access
""" for attr in self._get_cached_attrs():
pass if hasattr(self, attr):
val = getattr(self, attr)
if val is not None:
data[attr] = val
return data
def fill_cached_data(self, data: dict): def fill_cached_data(self, data: dict):
pass for attr in self._get_cached_attrs():
val = data.get(attr)
if val and hasattr(self, attr):
setattr(self, attr, val)
def can_be_run(self) -> bool: def can_be_run(self) -> bool:
return self.installed return self.installed and self.installation_dir
def is_trustable(self) -> bool: def is_trustable(self) -> bool:
return False return False

View File

@@ -0,0 +1,6 @@
from bauh.commons.system import SimpleProcess
from bauh.gems.web import NATIVEFIER_BIN_PATH
def install(url: str, name: str, output_dir: str, electron_version: str, cwd: str) -> SimpleProcess:
return SimpleProcess([NATIVEFIER_BIN_PATH, url, '--name', name, '-e', electron_version, output_dir], cwd=cwd)

View File

@@ -1,5 +1,17 @@
gem.web.info=It allows to install Web applications on your system gem.web.info=It allows to install Web applications on your system
web.environment.install=Installing {} web.environment.install=Installing {}
web.waiting.env_updater=Updating environment web.waiting.env_updater=Updating environment
web.env.checking=Checking the Web application installation environment web.env.checking=Checking the Web installation environment
web.env.error=It seems there are issues with the Web application 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.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 {}
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

View File

@@ -1,5 +1,17 @@
gem.web.info=Permite instalar aplicações Web no seu sistema gem.web.info=Permite instalar aplicações Web no seu sistema
web.environment.nativefier=Instalando {} web.environment.nativefier=Instalando {}
web.waiting.env_updater=Atualizando ambiente web.waiting.env_updater=Atualizando ambiente
web.env.checking=Verificando o ambiente de instalação para aplicativos Web web.env.checking=Verificando o ambiente de instalação Web
web.env.error=Parece haver problemas com o ambiente de instalação para aplicativos Web. Não sera possível instalar {}. web.env.error=Parce que existem problemas com o ambiente de instalação Web. Não será possível instalar {}.
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 {}
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

View File

@@ -201,4 +201,5 @@ publisher.verified=verificat
mirror=mirall mirror=mirall
emulator=emulador emulator=emulador
do_not.install=no instal·leu do_not.install=no instal·leu
repository=dipòsit repository=dipòsit
details=detalls

View File

@@ -155,4 +155,5 @@ close=Schließen
publisher.verified=Verifiziert publisher.verified=Verifiziert
emulator=emulator emulator=emulator
do_not.install=nicht installieren do_not.install=nicht installieren
repository=repository repository=repository
details=details

View File

@@ -157,3 +157,4 @@ mirror=mirror
emulator=emulator emulator=emulator
do_not.install=don't install do_not.install=don't install
repository=repository repository=repository
details=details

View File

@@ -200,4 +200,5 @@ publisher.verified=verificado
mirror=espejo mirror=espejo
emulator=emulador emulator=emulador
do_not.install=no instalar do_not.install=no instalar
repository=repositorio repository=repositorio
details=detalles

View File

@@ -156,4 +156,5 @@ publisher.verified=verificato
mirror=specchio mirror=specchio
emulator=emulatore emulator=emulatore
do_not.install=non installare do_not.install=non installare
repository=deposito repository=deposito
details=dettagli

View File

@@ -203,4 +203,5 @@ publisher.verified=verificado
mirror=espelho mirror=espelho
emulator=emulador emulator=emulador
do_not.install=não instalar do_not.install=não instalar
repository=repositório repository=repositório
details=detalhes

View File

@@ -3,11 +3,11 @@ import os
from setuptools import setup, find_packages from setuptools import setup, find_packages
DESCRIPTION = ( DESCRIPTION = (
"Graphical interface to manage Flatpak, Snap, AppImage and AUR packages" "Graphical interface to manage Flatpak, Snap, AppImage, AUR and Web applications"
) )
AUTHOR = "Vinicius Moreira" AUTHOR = "bauh developers"
AUTHOR_EMAIL = "vinicius_fmoreira@hotmail.com" AUTHOR_EMAIL = "bauh4linux@gmail.com"
NAME = 'bauh' NAME = 'bauh'
URL = "https://github.com/vinifmor/" + NAME URL = "https://github.com/vinifmor/" + NAME