From aed6bb04e0f4aa5a3800f505ea3eee0180f50920 Mon Sep 17 00:00:00 2001 From: Vinicius Moreira Date: Mon, 7 Oct 2019 14:55:06 -0300 Subject: [PATCH] installing, uninstalling and launching --- bauh/api/abstract/controller.py | 29 ++++--- bauh/api/abstract/model.py | 2 +- bauh/gems/appimage/controller.py | 116 +++++++++++++++++-------- bauh/gems/appimage/model.py | 44 +++++++--- bauh/gems/appimage/query.py | 2 +- bauh/gems/appimage/resources/locale/en | 5 +- bauh/gems/appimage/resources/locale/es | 5 +- bauh/gems/appimage/resources/locale/pt | 5 +- bauh/gems/arch/controller.py | 2 - 9 files changed, 145 insertions(+), 65 deletions(-) diff --git a/bauh/api/abstract/controller.py b/bauh/api/abstract/controller.py index 6c3b1370..d27f0ea3 100644 --- a/bauh/api/abstract/controller.py +++ b/bauh/api/abstract/controller.py @@ -162,20 +162,29 @@ class SoftwareManager(ABC): :return: """ if self.context.disk_cache and pkg.supports_disk_cache(): + self.serialize_to_disk(pkg, icon_bytes, only_icon) - if not only_icon: - Path(pkg.get_disk_cache_path()).mkdir(parents=True, exist_ok=True) - data = pkg.get_data_to_cache() + def serialize_to_disk(self, pkg: SoftwarePackage, icon_bytes: bytes, only_icon: bool): + """ + Sames as above, but does not check if disk cache is enabled or supported by the package instance + :param pkg: + :param icon_bytes: + :param only_icon: + :return: + """ + if not only_icon: + Path(pkg.get_disk_cache_path()).mkdir(parents=True, exist_ok=True) + data = pkg.get_data_to_cache() - if data: - with open(pkg.get_disk_data_path(), 'w+') as f: - f.write(json.dumps(data)) + if data: + with open(pkg.get_disk_data_path(), 'w+') as f: + f.write(json.dumps(data)) - if icon_bytes: - Path(pkg.get_disk_cache_path()).mkdir(parents=True, exist_ok=True) + if icon_bytes: + Path(pkg.get_disk_cache_path()).mkdir(parents=True, exist_ok=True) - with open(pkg.get_disk_icon_path(), 'wb+') as f: - f.write(icon_bytes) + with open(pkg.get_disk_icon_path(), 'wb+') as f: + f.write(icon_bytes) @abstractmethod def requires_root(self, action: str, pkg: SoftwarePackage): diff --git a/bauh/api/abstract/model.py b/bauh/api/abstract/model.py index 188f87cb..5503fdcd 100644 --- a/bauh/api/abstract/model.py +++ b/bauh/api/abstract/model.py @@ -145,7 +145,7 @@ class SoftwarePackage(ABC): pass @abstractmethod - def can_be_run(self) -> str: + def can_be_run(self) -> bool: """ :return: whether the app can be run via the GUI """ diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py index 74add872..01ce4805 100644 --- a/bauh/gems/appimage/controller.py +++ b/bauh/gems/appimage/controller.py @@ -1,7 +1,9 @@ +import json import os import re import shutil import sqlite3 +import subprocess from pathlib import Path from typing import Set, Type, List @@ -10,17 +12,18 @@ from bauh.api.abstract.controller import SoftwareManager, SearchResult from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion +from bauh.api.abstract.view import MessageType from bauh.api.constants import HOME_PATH from bauh.commons.html import bold -from bauh.commons.system import SystemProcess, new_subprocess, ProcessHandler +from bauh.commons.system import SystemProcess, new_subprocess, ProcessHandler, run_cmd from bauh.gems.appimage import query, INSTALLATION_PATH from bauh.gems.appimage.model import AppImage -DB_PATH = '{}/{}'.format(HOME_PATH, '.cache/bauh/appimage/appimage.db') +DB_PATH = '{}/{}'.format(HOME_PATH, '.local/share/bauh/appimage/appimage.db') DESKTOP_ENTRIES_PATH = '{}/.local/share/applications'.format(HOME_PATH) -RE_DESKTOP_EXEC = re.compile(r'Exec\s+=\s+.+\n') -RE_DESKTOP_ICON = re.compile(r'Icon\s+=\s+.+\n') +RE_DESKTOP_EXEC = re.compile(r'Exec\s*=\s*.+\n') +RE_DESKTOP_ICON = re.compile(r'Icon\s*=\s*.+\n') RE_ICON_ENDS_WITH = re.compile(r'.+\.(png|svg)$') @@ -54,12 +57,28 @@ class AppImageManager(SoftwareManager): res.new.append(app) finally: connection.close() + else: + self.logger.warning('Could not get a connection from the local database at {}'.format(DB_PATH)) res.total = len(res.installed) + len(res.new) 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 = None) -> SearchResult: - return SearchResult([], [], 0) + res = SearchResult([], [], 0) + + if os.path.exists(INSTALLATION_PATH): + installed = run_cmd('ls {}*/data.json'.format(INSTALLATION_PATH), print_error=False) + + if installed: + for path in installed.split('\n'): + if path: + with open(path) as f: + app = AppImage(installed=True, **json.loads(f.read())) + + res.installed.append(app) + + res.total = len(res.installed) + return res def downgrade(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool: pass @@ -68,7 +87,18 @@ class AppImageManager(SoftwareManager): pass def uninstall(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool: - pass + if os.path.exists(pkg.get_disk_cache_path()): + handler = ProcessHandler(watcher) + + if not handler.handle(SystemProcess(new_subprocess(['rm', '-rf', pkg.get_disk_cache_path()]))): + watcher.show_message(title=self.i18n['error'], body=self.i18n['appimage.uninstall.error.remove_folder'].format(bold(pkg.get_disk_cache_path()))) + return False + + de_path = self._gen_desktop_entry_path(pkg) + if os.path.exists(de_path): + os.remove(de_path) + + return True def get_managed_types(self) -> Set[Type[SoftwarePackage]]: return {AppImage} @@ -91,6 +121,12 @@ class AppImageManager(SoftwareManager): if f.endswith('.desktop'): return f + def _find_appimage_file(self, folder: str) -> str: + for r, d, files in os.walk(folder): + for f in files: + if f.lower().endswith('.appimage'): + return '{}/{}'.format(folder, f) + def _find_icon_file(self, folder: str) -> str: for r, d, files in os.walk(folder): for f in files: @@ -112,49 +148,48 @@ class AppImageManager(SoftwareManager): permission_given = handler.handle(SystemProcess(new_subprocess(['chmod', 'a+x', file_path]))) if permission_given: - watcher.change_substatus('Reading content from {}'.format(bold(file_name))) - extracted = handler.handle(SystemProcess(new_subprocess([file_name, '--appimage-extract'], cwd=out_dir))) - if extracted: - watcher.change_substatus('Generating desktop entry') - extracted_folder = '{}/{}'.format(out_dir, 'squashfs-root') + watcher.change_substatus(self.i18n['appimage.install.extract'].format(bold(file_name))) - if os.path.exists(extracted_folder): - desktop_entry = self._find_desktop_file(extracted_folder) + handler.handle(SystemProcess(new_subprocess([file_path, '--appimage-extract'], cwd=out_dir))) - if desktop_entry: - with open(desktop_entry) as f: - de_content = f.read() + watcher.change_substatus(self.i18n['appimage.install.desktop_entry']) + extracted_folder = '{}/{}'.format(out_dir, 'squashfs-root') - de_content = RE_DESKTOP_EXEC.sub('Exec='.format(file_path), de_content) + if os.path.exists(extracted_folder): + desktop_entry = self._find_desktop_file(extracted_folder) - extracted_icon = self._find_icon_file(extracted_folder) + with open('{}/{}'.format(extracted_folder, desktop_entry)) as f: + de_content = f.read() - if extracted_icon: - icon_path = out_dir + '/' + extracted_icon.split('/')[-1] - shutil.copy(icon_path, icon_path) - de_content = RE_DESKTOP_ICON.sub('Icon='.format(icon_path), de_content) + de_content = RE_DESKTOP_EXEC.sub('Exec={}\n'.format(file_path), de_content) - Path(DESKTOP_ENTRIES_PATH).mkdir(parents=True, exist_ok=True) + extracted_icon = self._find_icon_file(extracted_folder) - with open('{}/bauh_appimage_{}.desktop'.format(DESKTOP_ENTRIES_PATH, pkg.name.lower()), 'w+') as f: - f.write(de_content) + if extracted_icon: + icon_path = out_dir + '/logo.' + extracted_icon.split('.')[-1] + shutil.copy('{}/{}'.format(extracted_folder, extracted_icon), icon_path) + de_content = RE_DESKTOP_ICON.sub('Icon={}\n'.format(icon_path), de_content) + pkg.icon_path = icon_path - return True - else: - pass - # todo generate new - else: - watcher.show_message(title=self.i18n['error'], - body='Could not find extracted the content from {}'.format( - bold(file_name))) + Path(DESKTOP_ENTRIES_PATH).mkdir(parents=True, exist_ok=True) + + with open(self._gen_desktop_entry_path(pkg), 'w+') as f: + f.write(de_content) + + shutil.rmtree(extracted_folder) + return True else: watcher.show_message(title=self.i18n['error'], - body='Could not extract content from {}'.format(bold(file_name))) + body='Could extract content from {}'.format(bold(file_name)), + type_=MessageType.ERROR) handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) return False + def _gen_desktop_entry_path(self, app: AppImage) -> str: + return '{}/bauh_appimage_{}.desktop'.format(DESKTOP_ENTRIES_PATH, app.name.lower()) + def is_enabled(self) -> bool: return self.enabled @@ -187,5 +222,14 @@ class AppImageManager(SoftwareManager): return True def launch(self, pkg: AppImage): - # TODO - pass + installation_dir = pkg.get_disk_cache_path() + if os.path.exists(installation_dir): + appimag_path = self._find_appimage_file(installation_dir) + + if appimag_path: + subprocess.Popen([appimag_path]) + else: + self.logger.error("Could not find the AppImage file of '{}' in '{}'".format(pkg.name, installation_dir)) + + def cache_to_disk(self, pkg: SoftwarePackage, icon_bytes: bytes, only_icon: bool): + self.serialize_to_disk(pkg, icon_bytes, only_icon) diff --git a/bauh/gems/appimage/model.py b/bauh/gems/appimage/model.py index 1abfbc28..b97528d8 100644 --- a/bauh/gems/appimage/model.py +++ b/bauh/gems/appimage/model.py @@ -2,19 +2,25 @@ from typing import List from bauh.api.abstract.model import SoftwarePackage from bauh.commons import resource -from bauh.gems.appimage import ROOT_DIR +from bauh.gems.appimage import ROOT_DIR, INSTALLATION_PATH + +CACHED_ATTRS = {'name', 'description', 'version', 'icon_path', 'author'} class AppImage(SoftwarePackage): def __init__(self, name: str = None, description: str = None, github: str = None, source: str = None, version: str = None, - url_download: str = None, url_icon: str = None, license: str = None, pictures: List[str] = None): + url_download: str = None, url_icon: str = None, license: str = None, author: str = None, + pictures: List[str] = None, icon_path: str = None, installed: bool = False): super(AppImage, self).__init__(id=name, name=name, version=version, latest_version=version, - icon_url=url_icon, license=license, description=description) + icon_url=url_icon, license=license, description=description, + installed=installed) self.source = source self.github = github self.pictures = pictures self.url_download = url_download + self.icon_path = icon_path + self.author = author def can_be_installed(self): return not self.installed and self.url_download @@ -44,17 +50,31 @@ class AppImage(SoftwarePackage): return True def get_data_to_cache(self) -> dict: - # TODO - pass + data = {} + + for a in CACHED_ATTRS: + val = getattr(self, a) + if val: + data[a] = val + + return data def fill_cached_data(self, data: dict): - # TODO - pass + for a in CACHED_ATTRS: + val = data.get(a) - def can_be_run(self) -> str: - # TODO - return False + if val: + setattr(self, a, val) + + def can_be_run(self) -> bool: + return self.installed def get_publisher(self) -> str: - # TODO - pass + return self.author + + def get_disk_cache_path(self) -> str: + if self.name: + return INSTALLATION_PATH + self.name.lower() + + def get_disk_icon_path(self): + return self.icon_path diff --git a/bauh/gems/appimage/query.py b/bauh/gems/appimage/query.py index e025465a..1149aaf5 100644 --- a/bauh/gems/appimage/query.py +++ b/bauh/gems/appimage/query.py @@ -1,4 +1,4 @@ -ATTRS = ('name', 'description', 'github', 'source', 'version', 'url_download', 'url_icon', 'license') +ATTRS = ('name', 'description', 'github', 'source', 'version', 'url_download', 'url_icon', 'license', 'author') _SELECT_BASE = "SELECT {} FROM apps".format(','.join(ATTRS)) diff --git a/bauh/gems/appimage/resources/locale/en b/bauh/gems/appimage/resources/locale/en index dbdf2fc1..e928a8e1 100644 --- a/bauh/gems/appimage/resources/locale/en +++ b/bauh/gems/appimage/resources/locale/en @@ -1,2 +1,5 @@ gem.appimage.label=AppImage -appimage.install.permission=Giving execution permission to {} \ No newline at end of file +appimage.install.permission=Giving execution permission to {} +appimage.install.extract=Extracting the content from {} +appimage.install.desktop_entry=Generating a menu shortcut +appimage.uninstall.error.remove_folder=Could not remove the application installation directory {} \ No newline at end of file diff --git a/bauh/gems/appimage/resources/locale/es b/bauh/gems/appimage/resources/locale/es index 3e633ebf..7286c598 100644 --- a/bauh/gems/appimage/resources/locale/es +++ b/bauh/gems/appimage/resources/locale/es @@ -1,2 +1,5 @@ gem.appimage.label=AppImage -appimage.install.permission=Concediendo permiso de ejecución a {} \ No newline at end of file +appimage.install.permission=Concediendo permiso de ejecución a {} +appimage.install.extract=Extrayendo el contenido de {} +appimage.install.desktop_entry=Creando un atajo en el menú +appimage.uninstall.error.remove_folder=No se pudo eliminar el directorio de instalación de la aplicación {} \ No newline at end of file diff --git a/bauh/gems/appimage/resources/locale/pt b/bauh/gems/appimage/resources/locale/pt index 2d0be201..910dc563 100644 --- a/bauh/gems/appimage/resources/locale/pt +++ b/bauh/gems/appimage/resources/locale/pt @@ -1,2 +1,5 @@ gem.appimage.label=AppImage -appimage.install.permission=Concedendo permissão de execução para {} \ No newline at end of file +appimage.install.permission=Concedendo permissão de execução para {} +appimage.install.extract=Extraindo o conteúdo de {} +appimage.install.desktop_entry=Criando um atalho no menu +appimage.uninstall.error.remove_folder=Não foi possível remover o diretóri ode instalação do aplicativo {} \ No newline at end of file diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index 5de9f02c..7d73e1a3 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -394,8 +394,6 @@ class ArchManager(SoftwareManager): pre_download_files.append(f) if pre_download_files: - downloader = self.context.file_downloader.get_default_client_name() - for f in pre_download_files: fdata = f.split('::')