[snap] feature -> new custom action 'change channel'

This commit is contained in:
Vinicius Moreira
2020-09-06 11:41:39 -03:00
parent c5e421d270
commit 2d395b608f
13 changed files with 126 additions and 30 deletions

View File

@@ -34,6 +34,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
<img src="https://raw.githubusercontent.com/vinifmor/bauh-files/master/pictures/releases/0.9.7/snap_channels.png"> <img src="https://raw.githubusercontent.com/vinifmor/bauh-files/master/pictures/releases/0.9.7/snap_channels.png">
</p> </p>
- new custom action **Change channel**: allows to change the current channel of an installed Snap
<p align="center">
<img src="https://raw.githubusercontent.com/vinifmor/bauh-files/master/pictures/releases/0.9.7/snap_change_channel.png">
</p>
### Improvements ### Improvements
- AppImage - AppImage
- Manual file installation/upgrade: - Manual file installation/upgrade:

View File

@@ -119,7 +119,9 @@ installation_level: null # defines a default installation level: user or system.
#### Snap (snap) #### Snap (snap)
- Supported actions: search, install, uninstall, launch, downgrade - 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: - 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 install_channel: false # it allows to select an available channel during the application installation. Default: false

View File

@@ -12,6 +12,7 @@ from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpda
SuggestionPriority, CustomSoftwareAction, PackageStatus SuggestionPriority, CustomSoftwareAction, PackageStatus
from bauh.api.abstract.view import SingleSelectComponent, SelectViewType, InputOption, ViewComponent, PanelComponent, \ from bauh.api.abstract.view import SingleSelectComponent, SelectViewType, InputOption, ViewComponent, PanelComponent, \
FormComponent FormComponent
from bauh.api.exception import NoInternetException
from bauh.commons import resource, internet from bauh.commons import resource, internet
from bauh.commons.category import CategoriesDownloader from bauh.commons.category import CategoriesDownloader
from bauh.commons.config import save_config 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()), icon_path=resource.get_path('img/refresh.svg', context.get_view_path()),
manager_method='refresh', manager_method='refresh',
requires_root=True, 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): def _fill_categories(self, app: SnapApplication):
@@ -253,6 +260,28 @@ class SnapManager(SoftwareManager):
def refresh(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> bool: 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] 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): def _start_category_task(self, task_man: TaskManager):
if task_man: if task_man:
task_man.register_task('snap_cats', self.i18n['task.download_categories'].format('Snap'), get_icon_path()) task_man.register_task('snap_cats', self.i18n['task.download_categories'].format('Snap'), get_icon_path())
@@ -425,8 +454,8 @@ class SnapManager(SoftwareManager):
except: except:
return False, [traceback.format_exc()] return False, [traceback.format_exc()]
def _request_channel_installation(self, pkg: SnapApplication, snap_config: dict, snapd_client: SnapdClient, watcher: ProcessWatcher) -> Optional[str]: 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['install_channel']: if snap_config is None or snap_config['install_channel']:
try: try:
data = [r for r in snapd_client.find_by_name(pkg.name) if r['name'] == pkg.name] data = [r for r in snapd_client.find_by_name(pkg.name) if r['name'] == pkg.name]
except: except:
@@ -439,11 +468,31 @@ class SnapManager(SoftwareManager):
if not data[0].get('channels'): if not data[0].get('channels'):
self.logger.info("No channel available for '{}'. Skipping selection.".format(pkg.name)) self.logger.info("No channel available for '{}'. Skipping selection.".format(pkg.name))
else: else:
opts = [InputOption(label=c, value=c) for c in sorted(data[0]['channels'].keys())] if pkg.channel:
def_opt = [o for o in opts if o.value == 'latest/{}'.format(data[0].get('channel', 'stable'))] 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='', select = SingleSelectComponent(label='',
options=opts, 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) type_=SelectViewType.RADIO)
if not watcher.request_confirmation(title=self.i18n['snap.install.available_channels.title'], if not watcher.request_confirmation(title=self.i18n['snap.install.available_channels.title'],

View File

@@ -1,4 +1,8 @@
gem.snap.info=Aplicacions publicades a https://snapcraft.io/store 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.confirm=Actualitza {} ?
snap.action.refresh.label=Actualitza snap.action.refresh.label=Actualitza
snap.action.refresh.status=Sestà actualitzant snap.action.refresh.status=Sestà actualitzant

View File

@@ -1,4 +1,8 @@
gem.snap.info=Anwendungen von https://snapcraft.io/store 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.confirm=Erneuern {} ?
snap.action.refresh.label=Erneuern snap.action.refresh.label=Erneuern
snap.action.refresh.status=Erneuern snap.action.refresh.status=Erneuern

View File

@@ -1,4 +1,8 @@
gem.snap.info=Applications published at https://snapcraft.io/store 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.confirm=Refresh {} ?
snap.action.refresh.label=Refresh snap.action.refresh.label=Refresh
snap.action.refresh.status=Refreshing snap.action.refresh.status=Refreshing

View File

@@ -1,4 +1,8 @@
gem.snap.info=Aplicativos publicados en https://snapcraft.io/store 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.confirm=Actualizar {} ?
snap.action.refresh.label=Actualizar snap.action.refresh.label=Actualizar
snap.action.refresh.status=Actualizando snap.action.refresh.status=Actualizando

View File

@@ -1,4 +1,8 @@
gem.snap.info=Applicazioni pubblicate su https://snapcraft.io/store 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.confirm=Ripristina {} ?
snap.action.refresh.label=Ripristina snap.action.refresh.label=Ripristina
snap.action.refresh.status=Ripristinare snap.action.refresh.status=Ripristinare

View File

@@ -1,4 +1,8 @@
gem.snap.info=Aplicativos publicados em https://snapcraft.io/store 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.confirm=Atualizar {} ?
snap.action.refresh.label=Atualizar snap.action.refresh.label=Atualizar
snap.action.refresh.status=Atualizando snap.action.refresh.status=Atualizando

View File

@@ -1,4 +1,8 @@
gem.snap.info=Приложения, опубликованные на https://snapcraft.io/store 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.confirm=Обновить {} ?
snap.action.refresh.label=Обновить snap.action.refresh.label=Обновить
snap.action.refresh.status=Обновляется snap.action.refresh.status=Обновляется

View File

@@ -1,4 +1,8 @@
gem.snap.info=https://snapcraft.io/store adresinde yayınlanan uygulamalar 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.confirm=Yenile {} ?
snap.action.refresh.label=Yenile snap.action.refresh.label=Yenile
snap.action.refresh.status=Yenileniyor snap.action.refresh.status=Yenileniyor

View File

@@ -37,8 +37,13 @@ def downgrade_and_stream(app_name: str, root_password: str) -> SimpleProcess:
shell=True) shell=True)
def refresh_and_stream(app_name: str, root_password: str) -> SimpleProcess: def refresh_and_stream(app_name: str, root_password: str, channel: Optional[str] = None) -> SimpleProcess:
return SimpleProcess(cmd=[BASE_CMD, 'refresh', app_name], cmd = [BASE_CMD, 'refresh', app_name]
if channel:
cmd.append('--channel={}'.format(channel))
return SimpleProcess(cmd=cmd,
root_password=root_password, root_password=root_password,
error_phrases={'no updates available'}, error_phrases={'no updates available'},
shell=True) shell=True)

View File

@@ -11,7 +11,7 @@ from PyQt5.QtWidgets import QTableWidget, QTableView, QMenu, QAction, QTableWidg
QHeaderView, QLabel, QHBoxLayout, QToolBar, QSizePolicy QHeaderView, QLabel, QHBoxLayout, QToolBar, QSizePolicy
from bauh.api.abstract.cache import MemoryCache 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.commons.html import strip_html, bold
from bauh.view.qt import dialog from bauh.view.qt import dialog
from bauh.view.qt.colors import GREEN, BROWN from bauh.view.qt.colors import GREEN, BROWN
@@ -166,31 +166,34 @@ class AppsTable(QTableWidget):
menu_row.addAction(action_ignore_updates) menu_row.addAction(action_ignore_updates)
if bool(pkg.model.get_custom_supported_actions()): if bool(pkg.model.get_custom_supported_actions()):
for action in pkg.model.get_custom_supported_actions(): actions = [self._map_custom_action(pkg, a) for a in pkg.model.get_custom_supported_actions()]
item = QAction(self.i18n[action.i18n_label_key]) menu_row.addActions(actions)
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)
menu_row.adjustSize() menu_row.adjustSize()
menu_row.popup(QCursor.pos()) menu_row.popup(QCursor.pos())
menu_row.exec_() 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): def refresh(self, pkg: PackageView):
self._update_row(pkg, update_check_enabled=False, change_update_col=False) self._update_row(pkg, update_check_enabled=False, change_update_col=False)