diff --git a/CHANGELOG.md b/CHANGELOG.md index 00636e09..5cc4fe56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,14 @@ 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/). -## [0.9.6] 2020 +## [0.9.6] 2020-06 ### Improvements - AppImage - creating a symlink for the installed applications at **~.local/bin** (the link will have the same name of the application. if the link already exists, it will be named as 'app_name-appimage') [#122](https://github.com/vinifmor/bauh/issues/122) + - new initialization task that checks if the installed AppImage files have symlinks associated with (it creates new symlinks if they don't) +

+ +

- able to update AppImages with continuous releases - UI - not performing a full table refresh after installing and uninstalling packages diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py index 74bd401f..18bac25d 100644 --- a/bauh/gems/appimage/controller.py +++ b/bauh/gems/appimage/controller.py @@ -29,10 +29,10 @@ from bauh.commons.config import save_config from bauh.commons.html import bold from bauh.commons.system import SystemProcess, new_subprocess, ProcessHandler, run_cmd, SimpleProcess from bauh.gems.appimage import query, INSTALLATION_PATH, LOCAL_PATH, SUGGESTIONS_FILE, CONFIG_FILE, ROOT_DIR, \ - CONFIG_DIR, UPDATES_IGNORED_FILE, SYMLINKS_DIR + CONFIG_DIR, UPDATES_IGNORED_FILE, util from bauh.gems.appimage.config import read_config from bauh.gems.appimage.model import AppImage -from bauh.gems.appimage.worker import DatabaseUpdater +from bauh.gems.appimage.worker import DatabaseUpdater, SymlinksVerifier DB_APPS_PATH = '{}/{}'.format(str(Path.home()), '.local/share/bauh/appimage/apps.db') DB_RELEASES_PATH = '{}/{}'.format(str(Path.home()), '.local/share/bauh/appimage/releases.db') @@ -50,7 +50,7 @@ class AppImageManager(SoftwareManager): super(AppImageManager, self).__init__(context=context) self.i18n = context.i18n self.api_cache = context.cache_factory.new() - context.disk_loader_factory.map(AppImageManager, self.api_cache) + context.disk_loader_factory.map(AppImage, self.api_cache) self.enabled = True self.http_client = context.http_client self.logger = context.logger @@ -413,12 +413,6 @@ 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: @@ -524,8 +518,7 @@ class AppImageManager(SoftwareManager): except: traceback.print_exc() - self._add_symlink(pkg, file_path, watcher) - + SymlinksVerifier.create_symlink(app=pkg, file_path=file_path, logger=self.logger, watcher=watcher) return TransactionResult(success=True, installed=[pkg], removed=[]) else: watcher.show_message(title=self.i18n['error'], @@ -539,59 +532,6 @@ class AppImageManager(SoftwareManager): handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) return TransactionResult.fail() - def _add_symlink(self, app: AppImage, file_path: str, watcher: ProcessWatcher = None): - possible_names = (app.name.lower(), '{}-appimage'.format(app.name.lower())) - - if os.path.exists(SYMLINKS_DIR) and not os.path.isdir(SYMLINKS_DIR): - self.logger.warning("'{}' is not a directory. It will not be possible to create a symlink for '{}'".format(SYMLINKS_DIR, app.name)) - return - - available_system_dirs = (SYMLINKS_DIR, *(l for l in ('/usr/bin', '/usr/local/bin') if os.path.isdir(l))) - - # checking if the link already exists: - - available_name = None - for name in possible_names: - available_name = name - for sysdir in available_system_dirs: - if os.path.exists('{}/{}'.format(sysdir, name)): - available_name = None - break - - if available_name: - break - - if not available_name: - msg = "It was not possible to create a symlink for '{}' because the names {} are already available on the system".format(app.name, - possible_names) - self.logger.warning(msg) - if watcher: - watcher.print('[warning] {}'.format(msg)) - else: - try: - Path(SYMLINKS_DIR).mkdir(parents=True, exist_ok=True) - except: - self.logger.error("Could not create symlink directory '{}'".format(SYMLINKS_DIR)) - return - - symlink_path = '{}/{}'.format(SYMLINKS_DIR, available_name) - - try: - os.symlink(src=file_path, dst=symlink_path) - app.symlink = symlink_path - - msg = "symlink successfully created at {}".format(symlink_path) - self.logger.info(msg) - - if watcher: - watcher.print(msg) - except: - msg = "Could not create the symlink '{}'".format(symlink_path) - self.logger.error(msg) - - if watcher: - watcher.print('[error] {}'.format(msg)) - def _gen_desktop_entry_path(self, app: AppImage) -> str: return '{}/bauh_appimage_{}.desktop'.format(DESKTOP_ENTRIES_PATH, app.name.lower()) @@ -624,6 +564,9 @@ class AppImageManager(SoftwareManager): elif internet_available: updater.download_databases() # only once + symlink_check = SymlinksVerifier(taskman=task_manager, i18n=self.i18n, logger=self.logger) + symlink_check.start() + def list_updates(self, internet_available: bool) -> List[PackageUpdate]: res = self.read_installed(disk_loader=None, internet_available=internet_available) @@ -692,7 +635,7 @@ class AppImageManager(SoftwareManager): def launch(self, pkg: AppImage): installation_dir = pkg.get_disk_cache_path() if os.path.exists(installation_dir): - appimag_path = self._find_appimage_file(installation_dir) + appimag_path = util.find_appimage_file(installation_dir) if appimag_path: subprocess.Popen([appimag_path]) diff --git a/bauh/gems/appimage/resources/locale/ca b/bauh/gems/appimage/resources/locale/ca index dd4377eb..bfb71078 100644 --- a/bauh/gems/appimage/resources/locale/ca +++ b/bauh/gems/appimage/resources/locale/ca @@ -32,6 +32,7 @@ appimage.install.extract=S’està extraient el contingut de {} appimage.install.imported.rename_error=It was not possible to move the file {} to {} appimage.install.permission=S’està concedint el permís d’execució a {} appimage.task.db_update=Actualització de bases de dades +appimage.task.symlink_check=Checking symlinks appimage.uninstall.error.remove_folder=No s’ha pogut suprimir el directori d’instal·lació de l’aplicació {} gem.appimage.info=Aplicacions publicades a https://appimage.github.io/ gem.appimage.label=AppImage \ No newline at end of file diff --git a/bauh/gems/appimage/resources/locale/de b/bauh/gems/appimage/resources/locale/de index 0872e55d..e7a36881 100644 --- a/bauh/gems/appimage/resources/locale/de +++ b/bauh/gems/appimage/resources/locale/de @@ -32,6 +32,7 @@ appimage.install.extract=Entpacke Inhalt von {} appimage.install.imported.rename_error=It was not possible to move the file {} to {} appimage.install.permission=Ausführberechtigungen für {} appimage.task.db_update=Updating databases +appimage.task.symlink_check=Checking symlinks appimage.uninstall.error.remove_folder=Anwendungordner {} konnte nicht entfernt werden gem.appimage.info=Anwendungen veröffentlicht auf https://appimage.github.io/ gem.appimage.label=AppImage \ No newline at end of file diff --git a/bauh/gems/appimage/resources/locale/en b/bauh/gems/appimage/resources/locale/en index 7dc5af3d..5394cf31 100644 --- a/bauh/gems/appimage/resources/locale/en +++ b/bauh/gems/appimage/resources/locale/en @@ -32,6 +32,7 @@ appimage.install.extract=Extracting the content from {} appimage.install.imported.rename_error=It was not possible to move the file {} to {} appimage.install.permission=Giving execution permission to {} appimage.task.db_update=Updating databases +appimage.task.symlink_check=Checking symlinks appimage.uninstall.error.remove_folder=Could not remove the application installation directory {} gem.appimage.info=Applications published at https://appimage.github.io/ gem.appimage.label=AppImage \ No newline at end of file diff --git a/bauh/gems/appimage/resources/locale/es b/bauh/gems/appimage/resources/locale/es index 34ba9f13..47adc1b8 100644 --- a/bauh/gems/appimage/resources/locale/es +++ b/bauh/gems/appimage/resources/locale/es @@ -32,6 +32,7 @@ appimage.install.extract=Extrayendo el contenido de {} appimage.install.imported.rename_error=No fue posible mover el archivo {} a {} appimage.install.permission=Concediendo permiso de ejecución a {} appimage.task.db_update=Actualizando base de datos +appimage.task.symlink_check=Verificando links simbólicos appimage.uninstall.error.remove_folder=No se pudo eliminar el directorio de instalación de la aplicación {} gem.appimage.info=Aplicativos publicados en https://appimage.github.io/ gem.appimage.label=AppImage \ No newline at end of file diff --git a/bauh/gems/appimage/resources/locale/it b/bauh/gems/appimage/resources/locale/it index 491cb3e2..df393fef 100644 --- a/bauh/gems/appimage/resources/locale/it +++ b/bauh/gems/appimage/resources/locale/it @@ -32,6 +32,7 @@ appimage.install.extract=Estrarre il contenuto da {} appimage.install.imported.rename_error=It was not possible to move the file {} to {} appimage.install.permission=Gdare il permesso di esecuzione a {} appimage.task.db_update=Aggiornamento dei database +appimage.task.symlink_check=Checking symlinks appimage.uninstall.error.remove_folder=Impossibile rimuovere la directory di installazione dell'applicazione {} gem.appimage.info=Applicazioni pubblicate su https://appimage.github.io/ gem.appimage.label=AppImage \ No newline at end of file diff --git a/bauh/gems/appimage/resources/locale/pt b/bauh/gems/appimage/resources/locale/pt index 1b5cb021..be125a45 100644 --- a/bauh/gems/appimage/resources/locale/pt +++ b/bauh/gems/appimage/resources/locale/pt @@ -32,6 +32,7 @@ appimage.install.extract=Extraindo o conteúdo de {} appimage.install.imported.rename_error=Não foi possível mover o arquivo {} para {} appimage.install.permission=Concedendo permissão de execução para {} appimage.task.db_update=Atualizando base de dados +appimage.task.symlink_check=Verificando links simbólicos appimage.uninstall.error.remove_folder=Não foi possível remover o diretóri ode instalação do aplicativo {} gem.appimage.info=Aplicativos publicados em https://appimage.github.io/ gem.appimage.label=AppImage \ No newline at end of file diff --git a/bauh/gems/appimage/resources/locale/ru b/bauh/gems/appimage/resources/locale/ru index 4649bfe8..eaaac1f5 100644 --- a/bauh/gems/appimage/resources/locale/ru +++ b/bauh/gems/appimage/resources/locale/ru @@ -32,6 +32,7 @@ appimage.install.extract=Извлечь содержимое из {} appimage.install.imported.rename_error=Не удалось переместить файл {} в {} appimage.install.permission=Установить права на выполнение для {} appimage.task.db_update=Обновление базы данных +appimage.task.symlink_check=Checking symlinks appimage.uninstall.error.remove_folder=Не удалось удалить каталог приложения {} gem.appimage.info=Приложения, опубликованные на https://appimage.github.io/ gem.appimage.label=AppImage diff --git a/bauh/gems/appimage/resources/locale/tr b/bauh/gems/appimage/resources/locale/tr index d92b9e88..6dd8e0cb 100644 --- a/bauh/gems/appimage/resources/locale/tr +++ b/bauh/gems/appimage/resources/locale/tr @@ -32,6 +32,7 @@ appimage.install.extract={} 'den içerik ayıklanıyor appimage.install.imported.rename_error={} dosyasını {} klasörüne taşımak mümkün değildi appimage.install.permission={} için yürütme izni veriliyor appimage.task.db_update=Veritabanları güncelleniyor +appimage.task.symlink_check=Checking symlinks appimage.uninstall.error.remove_folder={} uygulama kurulum dizini kaldırılamadı gem.appimage.info=https://appimage.github.io/ adresinde yayınlanan uygulamalar gem.appimage.label=AppImage diff --git a/bauh/gems/appimage/util.py b/bauh/gems/appimage/util.py new file mode 100644 index 00000000..c717f5b4 --- /dev/null +++ b/bauh/gems/appimage/util.py @@ -0,0 +1,8 @@ +import os + + +def find_appimage_file(folder: str) -> str: + for r, d, files in os.walk(folder): + for f in files: + if f.lower().endswith('.appimage'): + return '{}/{}'.format(folder, f) diff --git a/bauh/gems/appimage/worker.py b/bauh/gems/appimage/worker.py index 5e494d00..7ffe047f 100644 --- a/bauh/gems/appimage/worker.py +++ b/bauh/gems/appimage/worker.py @@ -1,4 +1,5 @@ import glob +import json import logging import os import tarfile @@ -9,10 +10,11 @@ from threading import Thread import requests -from bauh.api.abstract.handler import TaskManager +from bauh.api.abstract.handler import TaskManager, ProcessWatcher from bauh.api.http import HttpClient from bauh.commons import internet -from bauh.gems.appimage import LOCAL_PATH, get_icon_path +from bauh.gems.appimage import LOCAL_PATH, get_icon_path, INSTALLATION_PATH, SYMLINKS_DIR, util +from bauh.gems.appimage.model import AppImage from bauh.view.util.translation import I18n @@ -102,3 +104,117 @@ class DatabaseUpdater(Thread): self.download_databases() self.logger.info('Sleeping') time.sleep(self.sleep) + + +class SymlinksVerifier(Thread): + + def __init__(self, taskman: TaskManager, i18n: I18n, logger: logging.Logger): + super(SymlinksVerifier, self).__init__(daemon=True) + self.taskman = taskman + self.i18n = i18n + self.logger = logger + self.task_id = 'appim_symlink_check' + + @staticmethod + def create_symlink(app: AppImage, file_path: str, logger: logging.Logger, watcher: ProcessWatcher = None): + logger.info("Creating a symlink for '{}'".format(app.name)) + possible_names = (app.name.lower(), '{}-appimage'.format(app.name.lower())) + + if os.path.exists(SYMLINKS_DIR) and not os.path.isdir(SYMLINKS_DIR): + logger.warning("'{}' is not a directory. It will not be possible to create a symlink for '{}'".format(SYMLINKS_DIR, app.name)) + return + + available_system_dirs = (SYMLINKS_DIR, *(l for l in ('/usr/bin', '/usr/local/bin') if os.path.isdir(l))) + + # checking if the link already exists: + + available_name = None + for name in possible_names: + available_name = name + for sysdir in available_system_dirs: + if os.path.exists('{}/{}'.format(sysdir, name)): + available_name = None + break + + if available_name: + break + + if not available_name: + msg = "It was not possible to create a symlink for '{}' because the names {} are already available on the system".format(app.name, + possible_names) + logger.warning(msg) + if watcher: + watcher.print('[warning] {}'.format(msg)) + else: + try: + Path(SYMLINKS_DIR).mkdir(parents=True, exist_ok=True) + except: + logger.error("Could not create symlink directory '{}'".format(SYMLINKS_DIR)) + return + + symlink_path = '{}/{}'.format(SYMLINKS_DIR, available_name) + + try: + os.symlink(src=file_path, dst=symlink_path) + app.symlink = symlink_path + + msg = "symlink successfully created at {}".format(symlink_path) + logger.info(msg) + + if watcher: + watcher.print(msg) + except: + msg = "Could not create the symlink '{}'".format(symlink_path) + logger.error(msg) + + if watcher: + watcher.print('[error] {}'.format(msg)) + + def run(self): + self.taskman.register_task(self.task_id, self.i18n['appimage.task.symlink_check'], get_icon_path()) + + if os.path.exists(INSTALLATION_PATH): + installed_files = glob.glob('{}/*/*.json'.format(INSTALLATION_PATH)) + + if installed_files: + self.logger.info("Checking installed AppImage files with no symlinks created") + + progress_per_file = (1/len(installed_files)) * 100 + total_progress = 0 + for json_file in installed_files: + with open(json_file) as f: + try: + data = json.loads(f.read()) + except: + self.logger.warning("Could not parse data from '{}'".format(json_file)) + data = None + + if data and not data.get('symlink'): + app = AppImage(**data, i18n=self.i18n) + file_path = util.find_appimage_file(app.install_dir) + + if file_path: + self.create_symlink(app, file_path, self.logger) + data['symlink'] = app.symlink + + # caching + try: + with open(json_file, 'w+') as f: + f.write(json.dumps(data)) + except: + self.logger.warning("Could not update cached data on '{}'".format(json_file)) + traceback.print_exc() + + else: + self.logger.warning("No AppImage file found on installation dir '{}'".format(file_path)) + + total_progress += progress_per_file + self.taskman.update_progress(self.task_id, total_progress, '') + + self.taskman.update_progress(self.task_id, 100, '') + self.taskman.finish_task(self.task_id) + return + + self.logger.info("No AppImage applications found. Aborting") + self.taskman.update_progress(self.task_id, 100, '') + self.taskman.finish_task(self.task_id) diff --git a/pictures/releases/0.9.6/appim_symlinks.png b/pictures/releases/0.9.6/appim_symlinks.png new file mode 100644 index 00000000..e9b6a710 Binary files /dev/null and b/pictures/releases/0.9.6/appim_symlinks.png differ