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