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)