[improvement][appimage] new initialization task that checks if the installed AppImage files have symlinks associated with

This commit is contained in:
Vinicius Moreira
2020-06-16 18:51:13 -03:00
parent 20d6f0fa35
commit c66bd4d900
13 changed files with 147 additions and 68 deletions

View File

@@ -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)
<p align="center">
<img src="https://raw.githubusercontent.com/vinifmor/bauh/staging/pictures/releases/0.9.6/appim_symlinks.png">
</p>
- able to update AppImages with continuous releases
- UI
- not performing a full table refresh after installing and uninstalling packages

View File

@@ -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])

View File

@@ -32,6 +32,7 @@ appimage.install.extract=Sestà extraient el contingut de {}
appimage.install.imported.rename_error=It was not possible to move the file {} to {}
appimage.install.permission=Sestà concedint el permís dexecució a {}
appimage.task.db_update=Actualització de bases de dades
appimage.task.symlink_check=Checking symlinks
appimage.uninstall.error.remove_folder=No sha pogut suprimir el directori dinstal·lació de laplicació {}
gem.appimage.info=Aplicacions publicades a https://appimage.github.io/
gem.appimage.label=AppImage

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB