diff --git a/CHANGELOG.md b/CHANGELOG.md index b8b277c5..00636e09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [0.9.6] 2020 ### 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) - able to update AppImages with continuous releases - UI - not performing a full table refresh after installing and uninstalling packages diff --git a/README.md b/README.md index c3522458..822899ca 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,7 @@ installation_level: null # defines a default installation level: user or system. - Supported sources: [AppImageHub](https://appimage.github.io) ( **applications with no releases published to GitHub are currently not available** ) - Installed applications are store at **~/.local/share/bauh/appimage/installed** - Desktop entries ( menu shortcuts ) of the installed applications are stored at **~/.local/share/applications** +- Symlinks are created at **~/.local/bin**. They have the same name of the application (if the name already exists, it will be created as 'app_name-appimage'. e.g: 'rpcs3-appimage') - Downloaded database files are stored at **~/.local/share/bauh/appimage** as **apps.db** and **releases.db** - Databases are always updated when bauh starts - Databases updater daemon running every 20 minutes ( it can be customized via the configuration file described below ) diff --git a/bauh/gems/appimage/__init__.py b/bauh/gems/appimage/__init__.py index 7875d807..6a577ed1 100644 --- a/bauh/gems/appimage/__init__.py +++ b/bauh/gems/appimage/__init__.py @@ -11,6 +11,7 @@ SUGGESTIONS_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master CONFIG_FILE = '{}/appimage.yml'.format(CONFIG_PATH) CONFIG_DIR = '{}/appimage'.format(CONFIG_PATH) UPDATES_IGNORED_FILE = '{}/updates_ignored.txt'.format(CONFIG_DIR) +SYMLINKS_DIR = '{}/.local/bin'.format(str(Path.home())) def get_icon_path() -> str: diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py index ab73c02d..74bd401f 100644 --- a/bauh/gems/appimage/controller.py +++ b/bauh/gems/appimage/controller.py @@ -29,7 +29,7 @@ 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 + CONFIG_DIR, UPDATES_IGNORED_FILE, SYMLINKS_DIR from bauh.gems.appimage.config import read_config from bauh.gems.appimage.model import AppImage from bauh.gems.appimage.worker import DatabaseUpdater @@ -330,6 +330,19 @@ class AppImageManager(SoftwareManager): self.revert_ignored_update(pkg) + if pkg.symlink and os.path.islink(pkg.symlink): + self.logger.info("Removing symlink '{}'".format(pkg.symlink)) + + try: + os.remove(pkg.symlink) + self.logger.info("symlink '{}' successfully removed".format(pkg.symlink)) + except: + msg = "could not remove symlink '{}'".format(pkg.symlink) + self.logger.error(msg) + + if watcher: + watcher.print("[error] {}".format(msg)) + return TransactionResult(success=True, installed=None, removed=[pkg]) def get_managed_types(self) -> Set[Type[SoftwarePackage]]: @@ -351,6 +364,9 @@ class AppImageManager(SoftwareManager): if categories: data['categories'] = [self.i18n.get('category.{}'.format(c.lower()), self.i18n.get(c, c)).capitalize() for c in data['categories']] + if data.get('symlink') and not os.path.islink(data['symlink']): + del data['symlink'] + return data def get_history(self, pkg: AppImage) -> PackageHistory: @@ -442,7 +458,7 @@ class AppImageManager(SoftwareManager): body=self.i18n['appimage.install.imported.rename_error'].format(bold(pkg.local_file_path.split('/')[-1]), bold(output)), type_=MessageType.ERROR) - return TransactionResult(success=False, installed=[], removed=[]) + return TransactionResult.fail() else: appimage_url = pkg.url_download_latest_version if pkg.update else pkg.url_download @@ -470,14 +486,14 @@ class AppImageManager(SoftwareManager): body=self.i18n['appimage.install.appimagelauncher.error'].format(appimgl=bold('AppImageLauncher'), app=bold(pkg.name)), type_=MessageType.ERROR) handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) - return TransactionResult(success=False, installed=[], removed=[]) + return TransactionResult.fail() except: watcher.show_message(title=self.i18n['error'], body=traceback.format_exc(), type_=MessageType.ERROR) traceback.print_exc() handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) - return TransactionResult(success=False, installed=[], removed=[]) + return TransactionResult.fail() watcher.change_substatus(self.i18n['appimage.install.desktop_entry']) extracted_folder = '{}/{}'.format(out_dir, 'squashfs-root') @@ -508,6 +524,8 @@ class AppImageManager(SoftwareManager): except: traceback.print_exc() + self._add_symlink(pkg, file_path, watcher) + return TransactionResult(success=True, installed=[pkg], removed=[]) else: watcher.show_message(title=self.i18n['error'], @@ -519,7 +537,60 @@ class AppImageManager(SoftwareManager): type_=MessageType.ERROR) handler.handle(SystemProcess(new_subprocess(['rm', '-rf', out_dir]))) - return TransactionResult(success=False, installed=[], removed=[]) + 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()) diff --git a/bauh/gems/appimage/model.py b/bauh/gems/appimage/model.py index ebee4f9b..c22bc3a5 100644 --- a/bauh/gems/appimage/model.py +++ b/bauh/gems/appimage/model.py @@ -6,7 +6,7 @@ from bauh.gems.appimage import ROOT_DIR, INSTALLATION_PATH from bauh.view.util.translation import I18n CACHED_ATTRS = {'name', 'description', 'version', 'url_download', 'author', 'license', 'source', - 'icon_path', 'github', 'categories', 'imported', 'install_dir'} + 'icon_path', 'github', 'categories', 'imported', 'install_dir', 'symlink'} class AppImage(SoftwarePackage): @@ -15,7 +15,8 @@ class AppImage(SoftwarePackage): url_download: str = None, url_icon: str = None, url_screenshot: str = None, license: str = None, author: str = None, categories=None, icon_path: str = None, installed: bool = False, url_download_latest_version: str = None, local_file_path: str = None, imported: bool = False, - i18n: I18n = None, install_dir: str = None, custom_actions: List[CustomSoftwareAction] = None, updates_ignored: bool = False, **kwargs): + i18n: I18n = None, install_dir: str = None, custom_actions: List[CustomSoftwareAction] = None, updates_ignored: bool = False, + symlink: str = None, **kwargs): super(AppImage, self).__init__(id=name, name=name, version=version, latest_version=version, icon_url=url_icon, license=license, description=description, installed=installed) @@ -33,6 +34,7 @@ class AppImage(SoftwarePackage): self.install_dir = install_dir self.custom_actions = custom_actions self.updates_ignored = updates_ignored + self.symlink = symlink def __repr__(self): return "{} (name={}, github={})".format(self.__class__.__name__, self.name, self.github) diff --git a/bauh/gems/appimage/resources/locale/ca b/bauh/gems/appimage/resources/locale/ca index d0a0b757..dd4377eb 100644 --- a/bauh/gems/appimage/resources/locale/ca +++ b/bauh/gems/appimage/resources/locale/ca @@ -23,6 +23,7 @@ appimage.info.icon_path=icona appimage.info.imported.false=No appimage.info.imported.true=Yes appimage.info.install_dir=Installation directory +appimage.info.symlink=Symlink appimage.info.url_download=URL del fitxer appimage.install.appimagelauncher.error={appimgl} no permet la instal·lació de {app}. Desinstal·leu {appimgl}, reinicieu el sistema i torneu a provar d’instal·lar {app}. appimage.install.desktop_entry=S’està creant una drecera del menú diff --git a/bauh/gems/appimage/resources/locale/de b/bauh/gems/appimage/resources/locale/de index dba7dc48..0872e55d 100644 --- a/bauh/gems/appimage/resources/locale/de +++ b/bauh/gems/appimage/resources/locale/de @@ -23,6 +23,7 @@ appimage.info.icon_path=icon appimage.info.imported.false=Nien appimage.info.imported.true=Ya appimage.info.install_dir=Installation directory +appimage.info.symlink=Symlink appimage.info.url_download=Datei URL appimage.install.appimagelauncher.error={appimgl} verhindert die installation von {app}. Deinstalliere {appimgl}, starte deinen Computer neu und versuche die installation von {app} erneut. appimage.install.desktop_entry=Menü-Shortcut erstellen diff --git a/bauh/gems/appimage/resources/locale/en b/bauh/gems/appimage/resources/locale/en index 93917a2c..7dc5af3d 100644 --- a/bauh/gems/appimage/resources/locale/en +++ b/bauh/gems/appimage/resources/locale/en @@ -23,6 +23,7 @@ appimage.info.icon_path=icon appimage.info.imported.false=No appimage.info.imported.true=Yes appimage.info.install_dir=Installation directory +appimage.info.symlink=Symlink appimage.info.url_download=File URL appimage.install.appimagelauncher.error={appimgl} is not allowing {app} to be installed. Uninstall {appimgl}, reboot your system and try to install {app} again. appimage.install.desktop_entry=Generating a menu shortcut diff --git a/bauh/gems/appimage/resources/locale/es b/bauh/gems/appimage/resources/locale/es index e7c08115..34ba9f13 100644 --- a/bauh/gems/appimage/resources/locale/es +++ b/bauh/gems/appimage/resources/locale/es @@ -23,6 +23,7 @@ appimage.info.icon_path=icono appimage.info.imported.false=No appimage.info.imported.true=Sí appimage.info.install_dir=Directorio de instalación +appimage.info.symlink=Link simbólico appimage.info.url_download=URL del archivo appimage.install.appimagelauncher.error={appimgl} no permite la instalación de {app}. Desinstale {appimgl}, reinicie su sistema e intente instalar {app} nuevamente. appimage.install.desktop_entry=Creando un atajo en el menú diff --git a/bauh/gems/appimage/resources/locale/it b/bauh/gems/appimage/resources/locale/it index cfb5b42f..491cb3e2 100644 --- a/bauh/gems/appimage/resources/locale/it +++ b/bauh/gems/appimage/resources/locale/it @@ -23,6 +23,7 @@ appimage.info.icon_path=icona appimage.info.imported.false=No appimage.info.imported.true=Yes appimage.info.install_dir=Installation directory +appimage.info.symlink=Symlink appimage.info.url_download=File URL appimage.install.appimagelauncher.error={appimgl} non consente l'installazione di {app}. Disinstallare {appimgl}, riavviare il sistema e provare a installare nuovamente {app}. appimage.install.desktop_entry=Genera un collegamento al menu diff --git a/bauh/gems/appimage/resources/locale/pt b/bauh/gems/appimage/resources/locale/pt index 49e38871..1b5cb021 100644 --- a/bauh/gems/appimage/resources/locale/pt +++ b/bauh/gems/appimage/resources/locale/pt @@ -23,6 +23,7 @@ appimage.info.icon_path=ícone appimage.info.imported.false=Não appimage.info.imported.true=Sim appimage.info.install_dir=Diretório de instalação +appimage.info.symlink=Link simbólico appimage.info.url_download=URL do arquivo appimage.install.appimagelauncher.error={appimgl} não está permitindo que o {app} seja instalado. Desinstale o {appimgl}, reinicie o sistema e tente instalar o {app} novamente. appimage.install.desktop_entry=Criando um atalho no menu diff --git a/bauh/gems/appimage/resources/locale/ru b/bauh/gems/appimage/resources/locale/ru index 951c6883..4649bfe8 100644 --- a/bauh/gems/appimage/resources/locale/ru +++ b/bauh/gems/appimage/resources/locale/ru @@ -23,6 +23,7 @@ appimage.info.icon_path=Значок appimage.info.imported.false=Нет appimage.info.imported.true=Да appimage.info.install_dir=Директория установки +appimage.info.symlink=Symlink appimage.info.url_download=URL Файла appimage.install.appimagelauncher.error={appimgl} не позволяет установить {app} . Удалите {appimgl}, перезагрузите ваш компьютер и попробуйте снова установить {app} . appimage.install.desktop_entry=Создать ярлык в меню diff --git a/bauh/gems/appimage/resources/locale/tr b/bauh/gems/appimage/resources/locale/tr index ac943471..d92b9e88 100644 --- a/bauh/gems/appimage/resources/locale/tr +++ b/bauh/gems/appimage/resources/locale/tr @@ -23,6 +23,7 @@ appimage.info.icon_path=simge appimage.info.imported.false=Hayır appimage.info.imported.true=Evet appimage.info.install_dir=Kurulum dizini +appimage.info.symlink=Symlink appimage.info.url_download=Dosya Bağlantısı appimage.install.appimagelauncher.error={appimgl}, {app} uygulamasının yüklenmesine izin vermiyor. {appimgl} yazılımını kaldırın, sisteminizi yeniden başlatın ve {app} yazılımını tekrar yüklemeyi deneyin. appimage.install.desktop_entry=Bir menü kısayolu oluşturuluyor