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

+ + - new custom action **Change channel**: allows to change the current channel of an installed Snap +

+ +

### Improvements - AppImage diff --git a/README.md b/README.md index 17fbbd61..390fd401 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,9 @@ installation_level: null # defines a default installation level: user or system. #### Snap (snap) - Supported actions: search, install, uninstall, launch, downgrade -- Custom actions: refresh +- Custom actions: + - refresh: tries to update the current Snap application revision + - change channel: allows to change the Snap application channel - 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 diff --git a/bauh/gems/snap/controller.py b/bauh/gems/snap/controller.py index f4a84f40..f0b9c471 100644 --- a/bauh/gems/snap/controller.py +++ b/bauh/gems/snap/controller.py @@ -12,6 +12,7 @@ from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpda SuggestionPriority, CustomSoftwareAction, PackageStatus from bauh.api.abstract.view import SingleSelectComponent, SelectViewType, InputOption, ViewComponent, PanelComponent, \ FormComponent +from bauh.api.exception import NoInternetException from bauh.commons import resource, internet from bauh.commons.category import CategoriesDownloader from bauh.commons.config import save_config @@ -47,7 +48,13 @@ class SnapManager(SoftwareManager): icon_path=resource.get_path('img/refresh.svg', context.get_view_path()), manager_method='refresh', requires_root=True, - i18n_confirm_key='snap.action.refresh.confirm') + i18n_confirm_key='snap.action.refresh.confirm'), + CustomSoftwareAction(i18n_status_key='snap.action.channel.status', + i18n_label_key='snap.action.channel.label', + i18n_confirm_key='snap.action.channel.confirm', + icon_path=resource.get_path('img/refresh.svg', context.get_view_path()), + manager_method='change_channel', + requires_root=True) ] def _fill_categories(self, app: SnapApplication): @@ -253,6 +260,28 @@ class SnapManager(SoftwareManager): def refresh(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> bool: return ProcessHandler(watcher).handle_simple(snap.refresh_and_stream(pkg.name, root_password))[0] + def change_channel(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> bool: + if not internet.is_available(): + raise NoInternetException() + + try: + channel = self._request_channel_installation(pkg=pkg, + snap_config=None, + snapd_client=SnapdClient(self.logger), + watcher=watcher, + exclude_current=True) + + if not channel: + watcher.show_message(title=self.i18n['snap.action.channel.label'], + body=self.i18n['snap.action.channel.error.no_channel']) + return True + + return ProcessHandler(watcher).handle_simple(snap.refresh_and_stream(app_name=pkg.name, + root_password=root_password, + channel=channel))[0] + except: + return True + def _start_category_task(self, task_man: TaskManager): if task_man: task_man.register_task('snap_cats', self.i18n['task.download_categories'].format('Snap'), get_icon_path()) @@ -425,8 +454,8 @@ class SnapManager(SoftwareManager): 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']: + def _request_channel_installation(self, pkg: SnapApplication, snap_config: Optional[dict], snapd_client: SnapdClient, watcher: ProcessWatcher, exclude_current: bool = False) -> Optional[str]: + if snap_config is None or snap_config['install_channel']: try: data = [r for r in snapd_client.find_by_name(pkg.name) if r['name'] == pkg.name] except: @@ -439,11 +468,31 @@ class SnapManager(SoftwareManager): 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'))] + if pkg.channel: + current_channel = pkg.channel if '/' in pkg.channel else 'latest/{}'.format(pkg.channel) + else: + current_channel = 'latest/{}'.format(data[0].get('channel', 'stable')) + + opts = [] + def_opt = None + for channel in sorted(data[0]['channels'].keys()): + if exclude_current: + if channel != current_channel: + opts.append(InputOption(label=channel, value=channel)) + else: + op = InputOption(label=channel, value=channel) + opts.append(op) + + if not def_opt and channel == current_channel: + def_opt = op + + if not opts: + self.logger.info("No different channel available for '{}'. Skipping selection.".format(pkg.name)) + return + select = SingleSelectComponent(label='', options=opts, - default_option=def_opt[0] if def_opt else opts[0], + default_option=def_opt if def_opt else opts[0], type_=SelectViewType.RADIO) if not watcher.request_confirmation(title=self.i18n['snap.install.available_channels.title'], diff --git a/bauh/gems/snap/resources/locale/ca b/bauh/gems/snap/resources/locale/ca index 14c84ee5..1341ff1c 100644 --- a/bauh/gems/snap/resources/locale/ca +++ b/bauh/gems/snap/resources/locale/ca @@ -1,4 +1,8 @@ gem.snap.info=Aplicacions publicades a https://snapcraft.io/store +snap.action.channel.confirm=Change channel of {} ? +snap.action.channel.error.no_channel=No available channel for {} +snap.action.channel.label=Change channel +snap.action.channel.status=Changing channel snap.action.refresh.confirm=Actualitza {} ? snap.action.refresh.label=Actualitza snap.action.refresh.status=S’està actualitzant diff --git a/bauh/gems/snap/resources/locale/de b/bauh/gems/snap/resources/locale/de index ed62a619..50666790 100644 --- a/bauh/gems/snap/resources/locale/de +++ b/bauh/gems/snap/resources/locale/de @@ -1,4 +1,8 @@ gem.snap.info=Anwendungen von https://snapcraft.io/store +snap.action.channel.error.no_channel=No available channel for {} +snap.action.channel.confirm=Change channel of {} ? +snap.action.channel.label=Change channel +snap.action.channel.status=Changing channel snap.action.refresh.confirm=Erneuern {} ? snap.action.refresh.label=Erneuern snap.action.refresh.status=Erneuern diff --git a/bauh/gems/snap/resources/locale/en b/bauh/gems/snap/resources/locale/en index 185466fb..d22604bb 100644 --- a/bauh/gems/snap/resources/locale/en +++ b/bauh/gems/snap/resources/locale/en @@ -1,4 +1,8 @@ gem.snap.info=Applications published at https://snapcraft.io/store +snap.action.channel.confirm=Change channel of {} ? +snap.action.channel.error.no_channel=No available channel for {} +snap.action.channel.label=Change channel +snap.action.channel.status=Changing channel snap.action.refresh.confirm=Refresh {} ? snap.action.refresh.label=Refresh snap.action.refresh.status=Refreshing diff --git a/bauh/gems/snap/resources/locale/es b/bauh/gems/snap/resources/locale/es index cfd58062..3a69fc4a 100644 --- a/bauh/gems/snap/resources/locale/es +++ b/bauh/gems/snap/resources/locale/es @@ -1,4 +1,8 @@ gem.snap.info=Aplicativos publicados en https://snapcraft.io/store +snap.action.channel.confirm=Cambiar canal de {}? +snap.action.channel.error.no_channel=No hay canal disponible para {} +snap.action.channel.status=Cambiando canal +snap.action.channel.label=Cambiar canal snap.action.refresh.confirm=Actualizar {} ? snap.action.refresh.label=Actualizar snap.action.refresh.status=Actualizando diff --git a/bauh/gems/snap/resources/locale/it b/bauh/gems/snap/resources/locale/it index a5448426..7675c75e 100644 --- a/bauh/gems/snap/resources/locale/it +++ b/bauh/gems/snap/resources/locale/it @@ -1,4 +1,8 @@ gem.snap.info=Applicazioni pubblicate su https://snapcraft.io/store +snap.action.channel.confirm=Change channel of {} ? +snap.action.channel.error.no_channel=No available channel for {} +snap.action.channel.label=Change channel +snap.action.channel.status=Changing channel snap.action.refresh.confirm=Ripristina {} ? snap.action.refresh.label=Ripristina snap.action.refresh.status=Ripristinare diff --git a/bauh/gems/snap/resources/locale/pt b/bauh/gems/snap/resources/locale/pt index 415175f9..67e983ce 100644 --- a/bauh/gems/snap/resources/locale/pt +++ b/bauh/gems/snap/resources/locale/pt @@ -1,4 +1,8 @@ gem.snap.info=Aplicativos publicados em https://snapcraft.io/store +snap.action.channel.confirm=Alterar canal de {} ? +snap.action.channel.error.no_channel=Nenhum canal disponível para {} +snap.action.channel.label=Alterar canal +snap.action.channel.status=Alterando canal snap.action.refresh.confirm=Atualizar {} ? snap.action.refresh.label=Atualizar snap.action.refresh.status=Atualizando diff --git a/bauh/gems/snap/resources/locale/ru b/bauh/gems/snap/resources/locale/ru index 747292d2..f6312c71 100644 --- a/bauh/gems/snap/resources/locale/ru +++ b/bauh/gems/snap/resources/locale/ru @@ -1,4 +1,8 @@ gem.snap.info=Приложения, опубликованные на https://snapcraft.io/store +snap.action.channel.confirm=Change channel of {} ? +snap.action.channel.error.no_channel=No available channel for {} +snap.action.channel.label=Change channel +snap.action.channel.status=Changing channel snap.action.refresh.confirm=Обновить {} ? snap.action.refresh.label=Обновить snap.action.refresh.status=Обновляется diff --git a/bauh/gems/snap/resources/locale/tr b/bauh/gems/snap/resources/locale/tr index 9b0861a3..dfb7d804 100644 --- a/bauh/gems/snap/resources/locale/tr +++ b/bauh/gems/snap/resources/locale/tr @@ -1,4 +1,8 @@ gem.snap.info=https://snapcraft.io/store adresinde yayınlanan uygulamalar +snap.action.channel.confirm=Change channel of {} ? +snap.action.channel.error.no_channel=No available channel for {} +snap.action.channel.label=Change channel +snap.action.channel.status=Changing channel snap.action.refresh.confirm=Yenile {} ? snap.action.refresh.label=Yenile snap.action.refresh.status=Yenileniyor diff --git a/bauh/gems/snap/snap.py b/bauh/gems/snap/snap.py index 244f61cd..01fe60fe 100644 --- a/bauh/gems/snap/snap.py +++ b/bauh/gems/snap/snap.py @@ -37,8 +37,13 @@ def downgrade_and_stream(app_name: str, root_password: str) -> SimpleProcess: shell=True) -def refresh_and_stream(app_name: str, root_password: str) -> SimpleProcess: - return SimpleProcess(cmd=[BASE_CMD, 'refresh', app_name], +def refresh_and_stream(app_name: str, root_password: str, channel: Optional[str] = None) -> SimpleProcess: + cmd = [BASE_CMD, 'refresh', app_name] + + if channel: + cmd.append('--channel={}'.format(channel)) + + return SimpleProcess(cmd=cmd, root_password=root_password, error_phrases={'no updates available'}, shell=True) diff --git a/bauh/view/qt/apps_table.py b/bauh/view/qt/apps_table.py index 380e05a4..4d70c958 100644 --- a/bauh/view/qt/apps_table.py +++ b/bauh/view/qt/apps_table.py @@ -11,7 +11,7 @@ from PyQt5.QtWidgets import QTableWidget, QTableView, QMenu, QAction, QTableWidg QHeaderView, QLabel, QHBoxLayout, QToolBar, QSizePolicy from bauh.api.abstract.cache import MemoryCache -from bauh.api.abstract.model import PackageStatus +from bauh.api.abstract.model import PackageStatus, CustomSoftwareAction from bauh.commons.html import strip_html, bold from bauh.view.qt import dialog from bauh.view.qt.colors import GREEN, BROWN @@ -166,31 +166,34 @@ class AppsTable(QTableWidget): menu_row.addAction(action_ignore_updates) if bool(pkg.model.get_custom_supported_actions()): - for action in pkg.model.get_custom_supported_actions(): - item = QAction(self.i18n[action.i18n_label_key]) - - if action.icon_path: - item.setIcon(QIcon(action.icon_path)) - - def custom_action(): - if action.i18n_confirm_key: - body = self.i18n[action.i18n_confirm_key].format(bold(pkg.model.name)) - else: - body = '{} ?'.format(self.i18n[action.i18n_label_key]) - - if dialog.ask_confirmation( - title=self.i18n[action.i18n_label_key], - body=self._parag(body), - i18n=self.i18n): - self.window.begin_execute_custom_action(pkg, action) - - item.triggered.connect(custom_action) - menu_row.addAction(item) + actions = [self._map_custom_action(pkg, a) for a in pkg.model.get_custom_supported_actions()] + menu_row.addActions(actions) menu_row.adjustSize() menu_row.popup(QCursor.pos()) menu_row.exec_() + def _map_custom_action(self, pkg: PackageView, action: CustomSoftwareAction) -> QAction: + item = QAction(self.i18n[action.i18n_label_key]) + + if action.icon_path: + item.setIcon(QIcon(action.icon_path)) + + def custom_action(): + if action.i18n_confirm_key: + body = self.i18n[action.i18n_confirm_key].format(bold(pkg.model.name)) + else: + body = '{} ?'.format(self.i18n[action.i18n_label_key]) + + if dialog.ask_confirmation( + title=self.i18n[action.i18n_label_key], + body=self._parag(body), + i18n=self.i18n): + self.window.begin_execute_custom_action(pkg, action) + + item.triggered.connect(custom_action) + return item + def refresh(self, pkg: PackageView): self._update_row(pkg, update_check_enabled=False, change_update_col=False)