diff --git a/CHANGELOG.md b/CHANGELOG.md index 562f0454..354bff3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

+- Snap + - new settings property **install_channel**: it allows to select an available channel during the application installation. Default: false. +

+ +

+ +

+ +

### Improvements - AppImage diff --git a/README.md b/README.md index cfa0039c..17fbbd61 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,10 @@ installation_level: null # defines a default installation level: user or system. - Supported actions: search, install, uninstall, launch, downgrade - Custom actions: refresh +- The configuration file is located at **~/.config/bauh/snap.yml** and it allows the following customizations: +``` +install_channel: false # it allows to select an available channel during the application installation. Default: false +``` - Required dependencies: - Any distro: **snapd** ( it must be enabled after its installation. Details at https://snapcraft.io/docs/installing-snapd ) diff --git a/bauh/api/abstract/controller.py b/bauh/api/abstract/controller.py index 67a61d29..deef917b 100644 --- a/bauh/api/abstract/controller.py +++ b/bauh/api/abstract/controller.py @@ -350,7 +350,7 @@ class SoftwareManager(ABC): """ pass - def save_settings(self, component: ViewComponent) -> Tuple[bool, List[str]]: + def save_settings(self, component: ViewComponent) -> Tuple[bool, Optional[List[str]]]: """ :return: a tuple with a bool informing if the settings were saved and a list of error messages """ diff --git a/bauh/gems/appimage/controller.py b/bauh/gems/appimage/controller.py index 686db47f..066c9708 100644 --- a/bauh/gems/appimage/controller.py +++ b/bauh/gems/appimage/controller.py @@ -723,7 +723,7 @@ class AppImageManager(SoftwareManager): return PanelComponent([FormComponent(updater_opts, self.i18n['appimage.config.db_updates'])]) - def save_settings(self, component: PanelComponent) -> Tuple[bool, List[str]]: + def save_settings(self, component: PanelComponent) -> Tuple[bool, Optional[List[str]]]: config = read_config() panel = component.components[0] diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index 93f7876b..2dbc0573 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -2682,7 +2682,7 @@ class ArchManager(SoftwareManager): return PanelComponent([FormComponent(fields, spaces=False)]) - def save_settings(self, component: PanelComponent) -> Tuple[bool, List[str]]: + def save_settings(self, component: PanelComponent) -> Tuple[bool, Optional[List[str]]]: config = read_config() form_install = component.components[0] diff --git a/bauh/gems/flatpak/controller.py b/bauh/gems/flatpak/controller.py index 81b86365..fc03d487 100644 --- a/bauh/gems/flatpak/controller.py +++ b/bauh/gems/flatpak/controller.py @@ -5,7 +5,7 @@ from datetime import datetime from math import floor from pathlib import Path from threading import Thread -from typing import List, Set, Type, Tuple +from typing import List, Set, Type, Tuple, Optional from bauh.api.abstract.controller import SearchResult, SoftwareManager, ApplicationContext, UpgradeRequirements, \ UpgradeRequirement, TransactionResult @@ -570,7 +570,7 @@ class FlatpakManager(SoftwareManager): return PanelComponent([FormComponent(fields, self.i18n['installation'].capitalize())]) - def save_settings(self, component: PanelComponent) -> Tuple[bool, List[str]]: + def save_settings(self, component: PanelComponent) -> Tuple[bool, Optional[List[str]]]: config = read_config() config['installation_level'] = component.components[0].components[0].get_selected() diff --git a/bauh/gems/snap/__init__.py b/bauh/gems/snap/__init__.py index bfd47893..0a3521c2 100644 --- a/bauh/gems/snap/__init__.py +++ b/bauh/gems/snap/__init__.py @@ -1,10 +1,11 @@ import os -from bauh.api.constants import CACHE_PATH +from bauh.api.constants import CACHE_PATH, CONFIG_PATH from bauh.commons import resource ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) SNAP_CACHE_PATH = CACHE_PATH + '/snap' +CONFIG_FILE = '{}/snap.yml'.format(CONFIG_PATH) CATEGORIES_FILE_PATH = SNAP_CACHE_PATH + '/categories.txt' URL_CATEGORIES_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/snap/categories.txt' SUGGESTIONS_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/snap/suggestions.txt' diff --git a/bauh/gems/snap/config.py b/bauh/gems/snap/config.py new file mode 100644 index 00000000..99d87637 --- /dev/null +++ b/bauh/gems/snap/config.py @@ -0,0 +1,7 @@ +from bauh.commons.config import read_config as read +from bauh.gems.snap import CONFIG_FILE + + +def read_config(update_file: bool = False) -> dict: + template = {'install_channel': False} + return read(CONFIG_FILE, template, update_file=update_file) diff --git a/bauh/gems/snap/controller.py b/bauh/gems/snap/controller.py index 0bc67433..b241e287 100644 --- a/bauh/gems/snap/controller.py +++ b/bauh/gems/snap/controller.py @@ -2,7 +2,7 @@ import re import time import traceback from threading import Thread -from typing import List, Set, Type, Optional +from typing import List, Set, Type, Optional, Tuple from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext, UpgradeRequirements, \ TransactionResult @@ -10,13 +10,17 @@ from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion, \ SuggestionPriority, CustomSoftwareAction, PackageStatus -from bauh.api.abstract.view import SingleSelectComponent, SelectViewType, InputOption +from bauh.api.abstract.view import SingleSelectComponent, SelectViewType, InputOption, ViewComponent, PanelComponent, \ + FormComponent from bauh.commons import resource, internet from bauh.commons.category import CategoriesDownloader +from bauh.commons.config import save_config from bauh.commons.html import bold from bauh.commons.system import SystemProcess, ProcessHandler, new_root_subprocess, get_human_size_str +from bauh.commons.view_utils import new_select from bauh.gems.snap import snap, URL_CATEGORIES_FILE, SNAP_CACHE_PATH, CATEGORIES_FILE_PATH, SUGGESTIONS_FILE, \ - get_icon_path, snapd + get_icon_path, snapd, CONFIG_FILE +from bauh.gems.snap.config import read_config from bauh.gems.snap.model import SnapApplication from bauh.gems.snap.snapd import SnapdClient @@ -175,7 +179,20 @@ class SnapManager(SoftwareManager): installed_names = {s['name'] for s in SnapdClient(self.logger).list_all_snaps()} - res, output = ProcessHandler(watcher).handle_simple(snap.install_and_stream(pkg.name, pkg.confinement, root_password)) + client = SnapdClient(self.logger) + snap_config = read_config() + + try: + channel = self._request_channel_installation(pkg=pkg, snap_config=snap_config, snapd_client=client, watcher=watcher) + pkg.channel = channel + except: + watcher.print('Aborted by user') + return TransactionResult.fail() + + res, output = ProcessHandler(watcher).handle_simple(snap.install_and_stream(app_name=pkg.name, + confinement=pkg.confinement, + root_password=root_password, + channel=channel)) if 'error:' in output: res = False @@ -247,6 +264,8 @@ class SnapManager(SoftwareManager): task_man.finish_task('snap_cats') def prepare(self, task_manager: TaskManager, root_password: str, internet_available: bool): + Thread(target=read_config, args=(True,), daemon=True).start() + CategoriesDownloader(id_='snap', manager=self, http_client=self.http_client, logger=self.logger, url_categories_file=URL_CATEGORIES_FILE, disk_cache_dir=SNAP_CACHE_PATH, categories_path=CATEGORIES_FILE_PATH, @@ -382,3 +401,56 @@ class SnapManager(SoftwareManager): def get_screenshots(self, pkg: SnapApplication) -> List[str]: return pkg.screenshots if pkg.has_screenshots() else [] + + def get_settings(self, screen_width: int, screen_height: int) -> ViewComponent: + snap_config = read_config() + + install_channel = new_select(label=self.i18n['snap.config.install_channel'], + opts=[(self.i18n['yes'].capitalize(), True, None), + (self.i18n['no'].capitalize(), False, None)], + value=bool(snap_config['install_channel']), + id_='install_channel', + max_width=200, + tip=self.i18n['snap.config.install_channel.tip']) + + return PanelComponent([FormComponent([install_channel], self.i18n['installation'].capitalize())]) + + def save_settings(self, component: ViewComponent) -> Tuple[bool, Optional[List[str]]]: + config = read_config() + config['install_channel'] = component.components[0].components[0].get_selected() + + try: + save_config(config, CONFIG_FILE) + return True, None + except: + return False, [traceback.format_exc()] + + def _request_channel_installation(self, pkg: SnapApplication, snap_config: dict, snapd_client: SnapdClient, watcher: ProcessWatcher) -> Optional[str]: + if snap_config['install_channel']: + try: + data = [r for r in snapd_client.find_by_name(pkg.name) if r['name'] == pkg.name] + except: + self.logger.warning("snapd client could not retrieve channels for '{}'".format(pkg.name)) + return + + if not data: + self.logger.warning("snapd client could find a match for name '{}' when retrieving its channels".format(pkg.name)) + else: + if not data[0].get('channels'): + self.logger.info("No channel available for '{}'. Skipping selection.".format(pkg.name)) + else: + opts = [InputOption(label=c, value=c) for c in sorted(data[0]['channels'].keys())] + def_opt = [o for o in opts if o.value == 'latest/{}'.format(data[0].get('channel', 'stable'))] + select = SingleSelectComponent(label='', + options=opts, + default_option=def_opt[0] if def_opt else opts[0], + type_=SelectViewType.RADIO) + + if not watcher.request_confirmation(title=self.i18n['snap.install.available_channels.title'], + body=self.i18n['snap.install.channel.body'] + ':', + components=[select], + confirmation_label=self.i18n['proceed'].capitalize(), + deny_label=self.i18n['cancel'].capitalize()): + raise Exception('aborted') + else: + return select.get_selected() diff --git a/bauh/gems/snap/resources/locale/ca b/bauh/gems/snap/resources/locale/ca index 935f9ae1..14c84ee5 100644 --- a/bauh/gems/snap/resources/locale/ca +++ b/bauh/gems/snap/resources/locale/ca @@ -2,6 +2,8 @@ gem.snap.info=Aplicacions publicades a https://snapcraft.io/store snap.action.refresh.confirm=Actualitza {} ? snap.action.refresh.label=Actualitza snap.action.refresh.status=S’està actualitzant +snap.config.install_channel=Display channels +snap.config.install_channel.tip=It allows to select one of the available channels for the given application to be installed snap.info.channel=channel snap.info.commands=ordres snap.info.contact=contacte @@ -18,6 +20,7 @@ snap.info.version=versió snap.install.available_channels.help=Seleccioneu-ne un si voleu continuar snap.install.available_channels.message=No hi ha un canal {} (stable) disponible per a {}. Però més avall n’hi ha els següents snap.install.available_channels.title=Canals disponibles +snap.install.channel.body=Select a channel to proceed snap.notification.snap.disable=Si no voleu utilitzar aplicacions snap, desmarqueu {} a {} snap.notification.snapd_unavailable=Sembla que no s’ha iniciat o activat {}. Els paquets {} no estaran disponibles. snap.notifications.api.unavailable=Sembla que l’API de {} no està disponible en aquests moments. No podreu cercar aplicacions {} noves. \ No newline at end of file diff --git a/bauh/gems/snap/resources/locale/de b/bauh/gems/snap/resources/locale/de index 7a672965..ed62a619 100644 --- a/bauh/gems/snap/resources/locale/de +++ b/bauh/gems/snap/resources/locale/de @@ -2,6 +2,8 @@ gem.snap.info=Anwendungen von https://snapcraft.io/store snap.action.refresh.confirm=Erneuern {} ? snap.action.refresh.label=Erneuern snap.action.refresh.status=Erneuern +snap.config.install_channel=Display channels +snap.config.install_channel.tip=It allows to select one of the available channels for the given application to be installed snap.info.channel=channel snap.info.commands=Kommandos snap.info.contact=Kontakt @@ -18,6 +20,7 @@ snap.info.version=Version snap.install.available_channels.help=Wähle einen aus um fortzufahren snap.install.available_channels.message=Es ist kein {} Channel verfügbar für {}. Es gibt jedoch folgende snap.install.available_channels.title=Verfügbare Channels +snap.install.channel.body=Select a channel to proceed snap.notification.snap.disable=Um keine Snap Anwerndungen zu nutzen deaktivieren {} unter {} snap.notification.snapd_unavailable={} wurde nicht gestartet oder ist deaktiviert. {} Pakete werden nicht verfügbar sein. snap.notifications.api.unavailable=Es scheint als ob die {} API aktuell nicht verfügbar ist. Die Suche nach neuen {} Anwendungen ist nicht möglich. \ No newline at end of file diff --git a/bauh/gems/snap/resources/locale/en b/bauh/gems/snap/resources/locale/en index 46ed39d0..185466fb 100644 --- a/bauh/gems/snap/resources/locale/en +++ b/bauh/gems/snap/resources/locale/en @@ -2,6 +2,8 @@ gem.snap.info=Applications published at https://snapcraft.io/store snap.action.refresh.confirm=Refresh {} ? snap.action.refresh.label=Refresh snap.action.refresh.status=Refreshing +snap.config.install_channel=Display channels +snap.config.install_channel.tip=It allows to select one of the available channels for the given application to be installed snap.info.channel=channel snap.info.commands=commands snap.info.contact=contact @@ -18,6 +20,7 @@ snap.info.version=version snap.install.available_channels.help=Select one if you want to continue snap.install.available_channels.message=There is no {} channel available for {}. But there are these below snap.install.available_channels.title=Available channels +snap.install.channel.body=Select a channel to proceed snap.notification.snap.disable=If you do not want to use Snap applications, uncheck {} in {} snap.notification.snapd_unavailable={} seems not to be started or enabled. {} packages will not be available. snap.notifications.api.unavailable=It seems the {} API is unavailable at the moment. It will not be possible to search for new {} applications. \ No newline at end of file diff --git a/bauh/gems/snap/resources/locale/es b/bauh/gems/snap/resources/locale/es index d2d612f3..cfd58062 100644 --- a/bauh/gems/snap/resources/locale/es +++ b/bauh/gems/snap/resources/locale/es @@ -2,6 +2,8 @@ gem.snap.info=Aplicativos publicados en https://snapcraft.io/store snap.action.refresh.confirm=Actualizar {} ? snap.action.refresh.label=Actualizar snap.action.refresh.status=Actualizando +snap.config.install_channel=Mostrar canales +snap.config.install_channel.tip=Permite seleccionar uno de los canales disponibles para la aplicación a instalar snap.info.channel=canal snap.info.commands=comandos snap.info.contact=contacto @@ -18,6 +20,7 @@ snap.info.version=versión snap.install.available_channels.help=Seleccione uno si desea continuar snap.install.available_channels.message=No hay un canal {} ( stable ) disponible para {}. Pero hay estos otros abajo snap.install.available_channels.title=Canales disponibles +snap.install.channel.body=Seleccione un canal para continuar snap.notification.snap.disable=Si no desea usar aplicativos Snap, desmarque {} en {} snap.notification.snapd_unavailable={} parece no estar inicializado o habilitado. Los paquetes {} estarán indisponibles. snap.notifications.api.unavailable=Parece que la API de {} no está disponible en este momento. No será posible buscar nuevos aplicativos {}. \ No newline at end of file diff --git a/bauh/gems/snap/resources/locale/it b/bauh/gems/snap/resources/locale/it index 254cfadc..a5448426 100644 --- a/bauh/gems/snap/resources/locale/it +++ b/bauh/gems/snap/resources/locale/it @@ -2,6 +2,8 @@ gem.snap.info=Applicazioni pubblicate su https://snapcraft.io/store snap.action.refresh.confirm=Ripristina {} ? snap.action.refresh.label=Ripristina snap.action.refresh.status=Ripristinare +snap.config.install_channel=Display channels +snap.config.install_channel.tip=It allows to select one of the available channels for the given application to be installed snap.info.channel=channel snap.info.commands=commands snap.info.contact=contact @@ -18,6 +20,7 @@ snap.info.version=version snap.install.available_channels.help=Selezionane uno se vuoi continuare snap.install.available_channels.message=Non esiste un {} canale disponibile per {}. Ma ci sono questi sotto snap.install.available_channels.title=Canali disponibili +snap.install.channel.body=Select a channel to proceed snap.notification.snap.disable=Se non si desidera utilizzare le applicazioni Snap, deselezionare {} in {} snap.notification.snapd_unavailable={} sembra non essere avviato o abilitato. {} i pacchetti non saranno disponibili. snap.notifications.api.unavailable=Sembra che l'API {} non sia al momento disponibile. Non sarà possibile cercare nuove {} applicazioni. \ No newline at end of file diff --git a/bauh/gems/snap/resources/locale/pt b/bauh/gems/snap/resources/locale/pt index 54fafa34..415175f9 100644 --- a/bauh/gems/snap/resources/locale/pt +++ b/bauh/gems/snap/resources/locale/pt @@ -2,6 +2,8 @@ gem.snap.info=Aplicativos publicados em https://snapcraft.io/store snap.action.refresh.confirm=Atualizar {} ? snap.action.refresh.label=Atualizar snap.action.refresh.status=Atualizando +snap.config.install_channel=Exibir canais +snap.config.install_channel.tip=Permite-se selecionar um dos canais disponíveis para o aplicativo a ser instalado snap.info.channel=canal snap.info.commands=comandos snap.info.contact=contato @@ -18,6 +20,7 @@ snap.info.version=versão snap.install.available_channels.help=Selecione algum se quiser continuar snap.install.available_channels.message=Não há um canal {} ( stable ) disponível para {}. Porém existem estes outros abaixo snap.install.available_channels.title=Canais disponíveis +snap.install.channel.body=Selecione um canal para continuar snap.notification.snap.disable=Se não deseja usar aplicativos Snap, desmarque {} em {} snap.notification.snapd_unavailable={} parece não estar inicializado ou habilitado. Os pacotes {} estarão indisponíveis. snap.notifications.api.unavailable=Parece que a API de {} está indisponível no momento. Não será possível buscar novos aplicativos {}. \ No newline at end of file diff --git a/bauh/gems/snap/resources/locale/ru b/bauh/gems/snap/resources/locale/ru index 76fc3d96..747292d2 100644 --- a/bauh/gems/snap/resources/locale/ru +++ b/bauh/gems/snap/resources/locale/ru @@ -2,6 +2,8 @@ gem.snap.info=Приложения, опубликованные на https://sn snap.action.refresh.confirm=Обновить {} ? snap.action.refresh.label=Обновить snap.action.refresh.status=Обновляется +snap.config.install_channel=Display channels +snap.config.install_channel.tip=It allows to select one of the available channels for the given application to be installed snap.info.channel=channel snap.info.commands=Команды snap.info.contact=Контакт @@ -18,6 +20,7 @@ snap.info.version=Версия snap.install.available_channels.help=Выберите один, если вы хотите продолжить snap.install.available_channels.message=Нет одного канала {}, доступного для {}. Но есть такие ниже snap.install.available_channels.title=Доступные каналы +snap.install.channel.body=Select a channel to proceed snap.notification.snap.disable=Если вы не хотите использовать приложения Snap, снимите флажок {} в {} snap.notification.snapd_unavailable={} кажется, не запускается и не включается. {} пакеты будут недоступны. snap.notifications.api.unavailable=Похоже, что API {} в данный момент недоступен. Невозможно будет искать новые приложения {}. \ No newline at end of file diff --git a/bauh/gems/snap/resources/locale/tr b/bauh/gems/snap/resources/locale/tr index 9799d921..9b0861a3 100644 --- a/bauh/gems/snap/resources/locale/tr +++ b/bauh/gems/snap/resources/locale/tr @@ -2,6 +2,8 @@ gem.snap.info=https://snapcraft.io/store adresinde yayınlanan uygulamalar snap.action.refresh.confirm=Yenile {} ? snap.action.refresh.label=Yenile snap.action.refresh.status=Yenileniyor +snap.config.install_channel=Display channels +snap.config.install_channel.tip=It allows to select one of the available channels for the given application to be installed snap.info.channel=channel snap.info.commands=komutlar snap.info.contact=iletişim @@ -18,6 +20,7 @@ snap.info.version=sürüm snap.install.available_channels.help=Devam etmek istiyorsanız birini seçin snap.install.available_channels.message={} için hiçbir {} kanalı yok. Ama aşağıda bunlar var snap.install.available_channels.title=Kullanılabilir kanallar +snap.install.channel.body=Select a channel to proceed snap.notification.snap.disable=Snap uygulamalarını kullanmak istemiyorsanız, {} içindeki {} işaretini kaldırın snap.notification.snapd_unavailable={} başlatılmamış veya etkinleştirilmemiş gibi görünüyor. {} paketler mevcut olmayacak. snap.notifications.api.unavailable=Görünüşe göre {} API şu anda kullanılamıyor. Yeni {} uygulamaları aramak mümkün olmayacak. diff --git a/bauh/gems/snap/snap.py b/bauh/gems/snap/snap.py index 0558df23..244f61cd 100644 --- a/bauh/gems/snap/snap.py +++ b/bauh/gems/snap/snap.py @@ -1,7 +1,7 @@ import os import subprocess from io import StringIO -from typing import Tuple +from typing import Tuple, Optional from bauh.commons.system import run_cmd, SimpleProcess @@ -18,13 +18,16 @@ def uninstall_and_stream(app_name: str, root_password: str) -> SimpleProcess: shell=True) -def install_and_stream(app_name: str, confinement: str, root_password: str) -> SimpleProcess: +def install_and_stream(app_name: str, confinement: str, root_password: str, channel: Optional[str] = None) -> SimpleProcess: install_cmd = [BASE_CMD, 'install', app_name] # default if confinement == 'classic': install_cmd.append('--classic') + if channel: + install_cmd.append('--channel={}'.format(channel)) + return SimpleProcess(install_cmd, root_password=root_password, shell=True) diff --git a/bauh/gems/web/controller.py b/bauh/gems/web/controller.py index 6361aec2..bfc0bfad 100644 --- a/bauh/gems/web/controller.py +++ b/bauh/gems/web/controller.py @@ -9,7 +9,7 @@ import traceback from math import floor from pathlib import Path from threading import Thread -from typing import List, Type, Set, Tuple +from typing import List, Type, Set, Tuple, Optional import requests import yaml @@ -996,7 +996,7 @@ class WebApplicationManager(SoftwareManager): return PanelComponent([form_env]) - def save_settings(self, component: PanelComponent) -> Tuple[bool, List[str]]: + def save_settings(self, component: PanelComponent) -> Tuple[bool, Optional[List[str]]]: config = read_config() form_env = component.components[0] diff --git a/bauh/view/qt/components.py b/bauh/view/qt/components.py index e247b12a..1e827cba 100644 --- a/bauh/view/qt/components.py +++ b/bauh/view/qt/components.py @@ -266,6 +266,7 @@ class RadioButtonQt(QRadioButton): self.model = model self.model_parent = model_parent self.toggled.connect(self._set_checked) + self.setCursor(QCursor(Qt.PointingHandCursor)) if model.icon_path: if model.icon_path.startswith('/'):