installing, uninstalling and launching

This commit is contained in:
Vinicius Moreira
2019-10-07 14:55:06 -03:00
parent 3975cd398b
commit aed6bb04e0
9 changed files with 145 additions and 65 deletions

View File

@@ -162,20 +162,29 @@ class SoftwareManager(ABC):
:return: :return:
""" """
if self.context.disk_cache and pkg.supports_disk_cache(): if self.context.disk_cache and pkg.supports_disk_cache():
self.serialize_to_disk(pkg, icon_bytes, only_icon)
if not only_icon: def serialize_to_disk(self, pkg: SoftwarePackage, icon_bytes: bytes, only_icon: bool):
Path(pkg.get_disk_cache_path()).mkdir(parents=True, exist_ok=True) """
data = pkg.get_data_to_cache() 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: if data:
with open(pkg.get_disk_data_path(), 'w+') as f: with open(pkg.get_disk_data_path(), 'w+') as f:
f.write(json.dumps(data)) f.write(json.dumps(data))
if icon_bytes: if icon_bytes:
Path(pkg.get_disk_cache_path()).mkdir(parents=True, exist_ok=True) Path(pkg.get_disk_cache_path()).mkdir(parents=True, exist_ok=True)
with open(pkg.get_disk_icon_path(), 'wb+') as f: with open(pkg.get_disk_icon_path(), 'wb+') as f:
f.write(icon_bytes) f.write(icon_bytes)
@abstractmethod @abstractmethod
def requires_root(self, action: str, pkg: SoftwarePackage): def requires_root(self, action: str, pkg: SoftwarePackage):

View File

@@ -145,7 +145,7 @@ class SoftwarePackage(ABC):
pass pass
@abstractmethod @abstractmethod
def can_be_run(self) -> str: def can_be_run(self) -> bool:
""" """
:return: whether the app can be run via the GUI :return: whether the app can be run via the GUI
""" """

View File

@@ -1,7 +1,9 @@
import json
import os import os
import re import re
import shutil import shutil
import sqlite3 import sqlite3
import subprocess
from pathlib import Path from pathlib import Path
from typing import Set, Type, List 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.disk import DiskCacheLoader
from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.handler import ProcessWatcher
from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion 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.api.constants import HOME_PATH
from bauh.commons.html import bold 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 import query, INSTALLATION_PATH
from bauh.gems.appimage.model import AppImage 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) DESKTOP_ENTRIES_PATH = '{}/.local/share/applications'.format(HOME_PATH)
RE_DESKTOP_EXEC = re.compile(r'Exec\s+=\s+.+\n') RE_DESKTOP_EXEC = re.compile(r'Exec\s*=\s*.+\n')
RE_DESKTOP_ICON = re.compile(r'Icon\s+=\s+.+\n') RE_DESKTOP_ICON = re.compile(r'Icon\s*=\s*.+\n')
RE_ICON_ENDS_WITH = re.compile(r'.+\.(png|svg)$') RE_ICON_ENDS_WITH = re.compile(r'.+\.(png|svg)$')
@@ -54,12 +57,28 @@ class AppImageManager(SoftwareManager):
res.new.append(app) res.new.append(app)
finally: finally:
connection.close() 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) res.total = len(res.installed) + len(res.new)
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 = None) -> SearchResult: 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: def downgrade(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool:
pass pass
@@ -68,7 +87,18 @@ class AppImageManager(SoftwareManager):
pass pass
def uninstall(self, pkg: AppImage, root_password: str, watcher: ProcessWatcher) -> bool: 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]]: def get_managed_types(self) -> Set[Type[SoftwarePackage]]:
return {AppImage} return {AppImage}
@@ -91,6 +121,12 @@ class AppImageManager(SoftwareManager):
if f.endswith('.desktop'): if f.endswith('.desktop'):
return f 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: def _find_icon_file(self, folder: str) -> str:
for r, d, files in os.walk(folder): for r, d, files in os.walk(folder):
for f in files: for f in files:
@@ -112,49 +148,48 @@ class AppImageManager(SoftwareManager):
permission_given = handler.handle(SystemProcess(new_subprocess(['chmod', 'a+x', file_path]))) permission_given = handler.handle(SystemProcess(new_subprocess(['chmod', 'a+x', file_path])))
if permission_given: 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(self.i18n['appimage.install.extract'].format(bold(file_name)))
watcher.change_substatus('Generating desktop entry')
extracted_folder = '{}/{}'.format(out_dir, 'squashfs-root')
if os.path.exists(extracted_folder): handler.handle(SystemProcess(new_subprocess([file_path, '--appimage-extract'], cwd=out_dir)))
desktop_entry = self._find_desktop_file(extracted_folder)
if desktop_entry: watcher.change_substatus(self.i18n['appimage.install.desktop_entry'])
with open(desktop_entry) as f: extracted_folder = '{}/{}'.format(out_dir, 'squashfs-root')
de_content = f.read()
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: de_content = RE_DESKTOP_EXEC.sub('Exec={}\n'.format(file_path), de_content)
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)
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: if extracted_icon:
f.write(de_content) 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 Path(DESKTOP_ENTRIES_PATH).mkdir(parents=True, exist_ok=True)
else:
pass with open(self._gen_desktop_entry_path(pkg), 'w+') as f:
# todo generate new f.write(de_content)
else:
watcher.show_message(title=self.i18n['error'], shutil.rmtree(extracted_folder)
body='Could not find extracted the content from {}'.format( return True
bold(file_name)))
else: else:
watcher.show_message(title=self.i18n['error'], 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]))) handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir])))
return False 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: def is_enabled(self) -> bool:
return self.enabled return self.enabled
@@ -187,5 +222,14 @@ class AppImageManager(SoftwareManager):
return True return True
def launch(self, pkg: AppImage): def launch(self, pkg: AppImage):
# TODO installation_dir = pkg.get_disk_cache_path()
pass 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)

View File

@@ -2,19 +2,25 @@ from typing import List
from bauh.api.abstract.model import SoftwarePackage from bauh.api.abstract.model import SoftwarePackage
from bauh.commons import resource 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): class AppImage(SoftwarePackage):
def __init__(self, name: str = None, description: str = None, github: str = None, source: str = None, version: str = None, 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, 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.source = source
self.github = github self.github = github
self.pictures = pictures self.pictures = pictures
self.url_download = url_download self.url_download = url_download
self.icon_path = icon_path
self.author = author
def can_be_installed(self): def can_be_installed(self):
return not self.installed and self.url_download return not self.installed and self.url_download
@@ -44,17 +50,31 @@ class AppImage(SoftwarePackage):
return True return True
def get_data_to_cache(self) -> dict: def get_data_to_cache(self) -> dict:
# TODO data = {}
pass
for a in CACHED_ATTRS:
val = getattr(self, a)
if val:
data[a] = val
return data
def fill_cached_data(self, data: dict): def fill_cached_data(self, data: dict):
# TODO for a in CACHED_ATTRS:
pass val = data.get(a)
def can_be_run(self) -> str: if val:
# TODO setattr(self, a, val)
return False
def can_be_run(self) -> bool:
return self.installed
def get_publisher(self) -> str: def get_publisher(self) -> str:
# TODO return self.author
pass
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

View File

@@ -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)) _SELECT_BASE = "SELECT {} FROM apps".format(','.join(ATTRS))

View File

@@ -1,2 +1,5 @@
gem.appimage.label=AppImage gem.appimage.label=AppImage
appimage.install.permission=Giving execution permission to {} 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 {}

View File

@@ -1,2 +1,5 @@
gem.appimage.label=AppImage gem.appimage.label=AppImage
appimage.install.permission=Concediendo permiso de ejecución a {} 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 {}

View File

@@ -1,2 +1,5 @@
gem.appimage.label=AppImage gem.appimage.label=AppImage
appimage.install.permission=Concedendo permissão de execução para {} 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 {}

View File

@@ -394,8 +394,6 @@ class ArchManager(SoftwareManager):
pre_download_files.append(f) pre_download_files.append(f)
if pre_download_files: if pre_download_files:
downloader = self.context.file_downloader.get_default_client_name()
for f in pre_download_files: for f in pre_download_files:
fdata = f.split('::') fdata = f.split('::')