mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-08 01:34:15 +02:00
Remove Snap support from Bearhub
This commit is contained in:
21
README.md
21
README.md
@@ -37,7 +37,7 @@ Large architectural changes or feature redesigns are not the immediate priority.
|
|||||||
|
|
||||||
Debian / Apt support is intentionally removed from Bearhub. Other non-Arch backends that remain in the codebase are secondary to the Arch-focused maintenance target.
|
Debian / Apt support is intentionally removed from Bearhub. Other non-Arch backends that remain in the codebase are secondary to the Arch-focused maintenance target.
|
||||||
|
|
||||||
**bauh** (baoo), formerly known as [fpakman](https://github.com/vinifmor/fpakman), is a graphical interface for managing your Linux software (packages/applications). In Bearhub, the maintained package formats are Arch Linux packages (including AUR), Flatpak, Snap, AppImage and Web applications.
|
**bauh** (baoo), formerly known as [fpakman](https://github.com/vinifmor/fpakman), is a graphical interface for managing your Linux software (packages/applications). In Bearhub, the maintained package formats are Arch Linux packages (including AUR), Flatpak, AppImage and Web applications.
|
||||||
|
|
||||||
Key features
|
Key features
|
||||||
- A management panel where you can: search, install, uninstall, upgrade, downgrade and launch your applications
|
- A management panel where you can: search, install, uninstall, upgrade, downgrade and launch your applications
|
||||||
@@ -63,7 +63,7 @@ Key features
|
|||||||
- [AppImage](#type_appimage)
|
- [AppImage](#type_appimage)
|
||||||
- [Arch packages/AUR](#type_arch)
|
- [Arch packages/AUR](#type_arch)
|
||||||
- [Flatpak](#type_flatpak)
|
- [Flatpak](#type_flatpak)
|
||||||
- [Snap](#type_snap)
|
|
||||||
- [Native Web applications](#type_web)
|
- [Native Web applications](#type_web)
|
||||||
7. [General settings](#settings)
|
7. [General settings](#settings)
|
||||||
- [Forbidden packaging formats](#forbidden_gems)
|
- [Forbidden packaging formats](#forbidden_gems)
|
||||||
@@ -126,7 +126,6 @@ makepkg -si
|
|||||||
- `xdg-utils`: to open URLs in the browser (`xdg-open`)
|
- `xdg-utils`: to open URLs in the browser (`xdg-open`)
|
||||||
- `sqlite`, `fuse2`, `fuse3`: AppImage support
|
- `sqlite`, `fuse2`, `fuse3`: AppImage support
|
||||||
- `flatpak`: Flatpak support
|
- `flatpak`: Flatpak support
|
||||||
- `snapd`: Snap support
|
|
||||||
- `pacman`: ArchLinux package management support
|
- `pacman`: ArchLinux package management support
|
||||||
- `python-lxml`, `python-beautifulsoup4`: Web apps support
|
- `python-lxml`, `python-beautifulsoup4`: Web apps support
|
||||||
- `python-venv`: [isolated installation](#inst_iso)
|
- `python-venv`: [isolated installation](#inst_iso)
|
||||||
@@ -252,7 +251,6 @@ suggestions:
|
|||||||
- `Unmark PKGBUILD as editable`: reverts the action described above. Action only available when the configuration property `edit_aur_pkgbuild` is not `false`.
|
- `Unmark PKGBUILD as editable`: reverts the action described above. Action only available when the configuration property `edit_aur_pkgbuild` is not `false`.
|
||||||
- `Allow reinstallation check`: it allows to check if a given AUR packages requires to be rebuilt
|
- `Allow reinstallation check`: it allows to check if a given AUR packages requires to be rebuilt
|
||||||
- `Ignore reinstallation check`: it does not to check if a given AUR packages requires to be rebuilt
|
- `Ignore reinstallation check`: it does not to check if a given AUR packages requires to be rebuilt
|
||||||
- `Check Snaps support`: checks if the Snapd services are properly enabled.
|
|
||||||
- `Reinstall` (AUR only): rebuilds an installed package.
|
- `Reinstall` (AUR only): rebuilds an installed package.
|
||||||
|
|
||||||
- If you have AUR added as a repository on you pacman configuration, make sure to disable bauh's support (through the settings described below)
|
- If you have AUR added as a repository on you pacman configuration, make sure to disable bauh's support (through the settings described below)
|
||||||
@@ -303,19 +301,6 @@ installation_level: null # defines a default installation level: "user" or "syst
|
|||||||
- Custom actions supported:
|
- Custom actions supported:
|
||||||
- **Full update**: it completely updates the Flatpak apps and components. Useful if you are having issues with runtime updates.
|
- **Full update**: it completely updates the Flatpak apps and components. Useful if you are having issues with runtime updates.
|
||||||
|
|
||||||
#### <a name="type_snap">Snap</a>
|
|
||||||
|
|
||||||
- Make sure **snapd** is properly installed and enabled on your system: https://snapcraft.io/docs/installing-snapd
|
|
||||||
- Extra 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
|
|
||||||
categories_exp: 24 # It defines the expiration time (in HOURS) of the Snaps categories mapping file stored in disc. Use 0 so that it is always updated during initialization.
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### <a name="type_web">Native Web applications</a>
|
#### <a name="type_web">Native Web applications</a>
|
||||||
- It allows the installation of Web applications by typing their addresses/URLs on the search bar
|
- It allows the installation of Web applications by typing their addresses/URLs on the search bar
|
||||||
|
|
||||||
@@ -452,7 +437,7 @@ appimage
|
|||||||
- [appimage](https://github.com/vinifmor/bauh-files/blob/master/appimage/suggestions.txt)
|
- [appimage](https://github.com/vinifmor/bauh-files/blob/master/appimage/suggestions.txt)
|
||||||
- [arch](https://github.com/vinifmor/bauh-files/blob/master/appimage/suggestions.txt)
|
- [arch](https://github.com/vinifmor/bauh-files/blob/master/appimage/suggestions.txt)
|
||||||
- [flatpak](https://github.com/vinifmor/bauh-files/blob/master/flatpak/suggestions.txt)
|
- [flatpak](https://github.com/vinifmor/bauh-files/blob/master/flatpak/suggestions.txt)
|
||||||
- [snap](https://github.com/vinifmor/bauh-files/blob/master/snap/suggestions.txt)
|
|
||||||
- [web](https://github.com/vinifmor/bauh-files/blob/master/web/env/v2/suggestions.yml)
|
- [web](https://github.com/vinifmor/bauh-files/blob/master/web/env/v2/suggestions.yml)
|
||||||
|
|
||||||
- Most of the files follow the pattern: `{priority_number}=${id or name}`
|
- Most of the files follow the pattern: `{priority_number}=${id or name}`
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ Name[de]=Anwendungen (Bearhub)
|
|||||||
Name[ru]=Приложения (Bearhub)
|
Name[ru]=Приложения (Bearhub)
|
||||||
Name[tr]=Paket Yönetici (Bearhub)
|
Name[tr]=Paket Yönetici (Bearhub)
|
||||||
Categories=System;
|
Categories=System;
|
||||||
Comment=Install and remove applications (AppImage, Arch, Flatpak, Snap, Web)
|
Comment=Install and remove applications (AppImage, Arch, Flatpak, Web)
|
||||||
Comment[pt]=Instale e remova aplicativos (AppImage, Arch, Flatpak, Snap, Web)
|
Comment[pt]=Instale e remova aplicativos (AppImage, Arch, Flatpak, Web)
|
||||||
Comment[es]=Instalar y eliminar aplicaciones (AppImage, Arch, Flatpak, Snap, Web)
|
Comment[es]=Instalar y eliminar aplicaciones (AppImage, Arch, Flatpak, Web)
|
||||||
Comment[it]=Installa e rimuovi applicazioni (AppImage, Arch, Flatpak, Snap, Web)
|
Comment[it]=Installa e rimuovi applicazioni (AppImage, Arch, Flatpak, Web)
|
||||||
Comment[de]=Anwendungen installieren und entfernen (AppImage, Arch, Flatpak, Snap, Web)
|
Comment[de]=Anwendungen installieren und entfernen (AppImage, Arch, Flatpak, Web)
|
||||||
Comment[ca]=Instal·lar i eliminar aplicacions (AppImage, Arch, Flatpak, Snap, Web)
|
Comment[ca]=Instal·lar i eliminar aplicacions (AppImage, Arch, Flatpak, Web)
|
||||||
Comment[ru]=Установка и удаление приложений (AppImage, Arch, Flatpak, Snap, Web)
|
Comment[ru]=Установка и удаление приложений (AppImage, Arch, Flatpak, Web)
|
||||||
Comment[tr]=Uygulama yükle/kaldır (AppImage, Arch, Flatpak, Snap, Web)
|
Comment[tr]=Uygulama yükle/kaldır (AppImage, Arch, Flatpak, Web)
|
||||||
Exec=/usr/bin/bearhub
|
Exec=/usr/bin/bearhub
|
||||||
Icon=bearhub
|
Icon=bearhub
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ Name[de]=Anwendungen (Bearhub tray)
|
|||||||
Name[ru]=Приложения (Bearhub tray)
|
Name[ru]=Приложения (Bearhub tray)
|
||||||
Name[tr]=Paket Yönetici (Bearhub tray)
|
Name[tr]=Paket Yönetici (Bearhub tray)
|
||||||
Categories=System;
|
Categories=System;
|
||||||
Comment=Install and remove applications (AppImage, Arch, Flatpak, Snap, Web)
|
Comment=Install and remove applications (AppImage, Arch, Flatpak, Web)
|
||||||
Comment[pt]=Instale e remova aplicativos (AppImage, Arch, Flatpak, Snap, Web)
|
Comment[pt]=Instale e remova aplicativos (AppImage, Arch, Flatpak, Web)
|
||||||
Comment[es]=Instalar y eliminar aplicaciones (AppImage, Arch, Flatpak, Snap, Web)
|
Comment[es]=Instalar y eliminar aplicaciones (AppImage, Arch, Flatpak, Web)
|
||||||
Comment[it]=Installa e rimuovi applicazioni (AppImage, Arch, Flatpak, Snap, Web)
|
Comment[it]=Installa e rimuovi applicazioni (AppImage, Arch, Flatpak, Web)
|
||||||
Comment[de]=Anwendungen installieren und entfernen (AppImage, Arch, Flatpak, Snap, Web)
|
Comment[de]=Anwendungen installieren und entfernen (AppImage, Arch, Flatpak, Web)
|
||||||
Comment[ca]=Instal·lar i eliminar aplicacions (AppImage, Arch, Flatpak, Snap, Web)
|
Comment[ca]=Instal·lar i eliminar aplicacions (AppImage, Arch, Flatpak, Web)
|
||||||
Comment[ru]=Установка и удаление приложений (AppImage, Arch, Flatpak, Snap, Web)
|
Comment[ru]=Установка и удаление приложений (AppImage, Arch, Flatpak, Web)
|
||||||
Comment[tr]=Uygulama yükle/kaldır (AppImage, Arch, Flatpak, Snap, Web)
|
Comment[tr]=Uygulama yükle/kaldır (AppImage, Arch, Flatpak, Web)
|
||||||
Exec=/usr/bin/bearhub-tray
|
Exec=/usr/bin/bearhub-tray
|
||||||
Icon=bearhub
|
Icon=bearhub
|
||||||
|
|||||||
@@ -3203,15 +3203,6 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
requires_root=True,
|
requires_root=True,
|
||||||
refresh=False,
|
refresh=False,
|
||||||
manager=self,
|
manager=self,
|
||||||
requires_confirmation=False),
|
|
||||||
'setup_snapd': CustomSoftwareAction(i18n_label_key='arch.custom_action.setup_snapd',
|
|
||||||
i18n_status_key='arch.custom_action.setup_snapd.status',
|
|
||||||
i18n_description_key='arch.custom_action.setup_snapd.desc',
|
|
||||||
manager_method='setup_snapd',
|
|
||||||
icon_path=get_icon_path(),
|
|
||||||
requires_root=False,
|
|
||||||
refresh=False,
|
|
||||||
manager=self,
|
|
||||||
requires_confirmation=False)
|
requires_confirmation=False)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3226,9 +3217,6 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
if bool(arch_config['repositories']):
|
if bool(arch_config['repositories']):
|
||||||
yield self._custom_actions['sys_up']
|
yield self._custom_actions['sys_up']
|
||||||
|
|
||||||
if pacman.is_snapd_installed():
|
|
||||||
yield self._custom_actions['setup_snapd']
|
|
||||||
|
|
||||||
def fill_sizes(self, pkgs: List[ArchPackage]):
|
def fill_sizes(self, pkgs: List[ArchPackage]):
|
||||||
installed, new, all_names, installed_names = [], [], [], []
|
installed, new, all_names, installed_names = [], [], [], []
|
||||||
|
|
||||||
@@ -3475,89 +3463,6 @@ class ArchManager(SoftwareManager, SettingsController):
|
|||||||
if self._remove_from_editable_pkgbuilds(pkg.name):
|
if self._remove_from_editable_pkgbuilds(pkg.name):
|
||||||
pkg.pkgbuild_editable = False
|
pkg.pkgbuild_editable = False
|
||||||
|
|
||||||
def setup_snapd(self, root_password: Optional[str], watcher: ProcessWatcher) -> bool:
|
|
||||||
# checking services
|
|
||||||
missing_items = []
|
|
||||||
for serv, active in system.check_enabled_services('snapd.service', 'snapd.socket').items():
|
|
||||||
if not active:
|
|
||||||
missing_items.append(InputOption(label=self.i18n['snap.custom_action.setup_snapd.service_disabled'].format("'{}'".format(serv)),
|
|
||||||
value='enable:{}'.format(serv),
|
|
||||||
read_only=True))
|
|
||||||
|
|
||||||
for serv, active in system.check_active_services('snapd.service', 'snapd.socket').items():
|
|
||||||
if not active:
|
|
||||||
missing_items.append(InputOption(label=self.i18n['snap.custom_action.setup_snapd.service_inactive'].format("'{}'".format(serv)),
|
|
||||||
value='start:{}'.format(serv),
|
|
||||||
read_only=True))
|
|
||||||
|
|
||||||
link = '/snap'
|
|
||||||
link_dest = '/var/lib/snapd/snap'
|
|
||||||
if not os.path.exists('/snap'):
|
|
||||||
missing_items.append(InputOption(label=self.i18n['snap.custom_action.setup_snapd.missing_link'].format("'{}'".format(link), "'{}'".format(link_dest)),
|
|
||||||
value='link:{}:{}'.format(link, link_dest),
|
|
||||||
read_only=True))
|
|
||||||
|
|
||||||
if missing_items:
|
|
||||||
actions = MultipleSelectComponent(label=self.i18n['snap.custom_action.setup_snapd.required_actions'],
|
|
||||||
options=missing_items,
|
|
||||||
default_options=set(missing_items),
|
|
||||||
max_per_line=1,
|
|
||||||
spaces=False)
|
|
||||||
if watcher.request_confirmation(title=self.i18n['confirmation'].capitalize(),
|
|
||||||
body='',
|
|
||||||
components=[actions],
|
|
||||||
confirmation_label=self.i18n['proceed'].capitalize(),
|
|
||||||
deny_label=self.i18n['cancel'].capitalize()):
|
|
||||||
|
|
||||||
valid_pwd, pwd = watcher.request_root_password()
|
|
||||||
|
|
||||||
if valid_pwd:
|
|
||||||
handler = ProcessHandler(watcher)
|
|
||||||
for a in missing_items:
|
|
||||||
action = a.value.split(':')
|
|
||||||
|
|
||||||
if action[0] == 'enable':
|
|
||||||
msg = 'Enabling service {}'.format(action[1])
|
|
||||||
watcher.print(msg)
|
|
||||||
self.logger.info(msg)
|
|
||||||
proc = SimpleProcess(['systemctl', 'enable', '--now', action[1]], root_password=pwd)
|
|
||||||
elif action[0] == 'start':
|
|
||||||
msg = 'Starting service {}'.format(action[1])
|
|
||||||
watcher.print(msg)
|
|
||||||
self.logger.info(msg)
|
|
||||||
proc = SimpleProcess(['systemctl', 'start', action[1]], root_password=pwd)
|
|
||||||
elif action[0] == 'link':
|
|
||||||
msg = 'Creating symbolic link {} for {}'.format(action[1], action[2])
|
|
||||||
watcher.print(msg)
|
|
||||||
self.logger.info(msg)
|
|
||||||
proc = SimpleProcess(['ln', '-s', action[2], action[1]], root_password=pwd)
|
|
||||||
else:
|
|
||||||
msg = "Wrong action '{}'".format(action)
|
|
||||||
watcher.print(msg)
|
|
||||||
self.logger.warning(msg)
|
|
||||||
proc = None
|
|
||||||
|
|
||||||
if not proc:
|
|
||||||
return False
|
|
||||||
|
|
||||||
success, output = handler.handle_simple(proc)
|
|
||||||
|
|
||||||
if not success:
|
|
||||||
watcher.show_message(title=self.i18n['error'].capitalize(),
|
|
||||||
body=output,
|
|
||||||
type_=MessageType.ERROR)
|
|
||||||
return False
|
|
||||||
|
|
||||||
watcher.show_message(title=self.i18n['snap.custom_action.setup_snapd.ready'],
|
|
||||||
body=self.i18n['snap.custom_action.setup_snapd.ready.body'],
|
|
||||||
type_=MessageType.INFO)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
watcher.show_message(title=self.i18n['snap.custom_action.setup_snapd.ready'],
|
|
||||||
body=self.i18n['snap.custom_action.setup_snapd.ready.body'],
|
|
||||||
type_=MessageType.INFO)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _gen_custom_pkgbuild_if_required(self, context: TransactionContext) -> Optional[str]:
|
def _gen_custom_pkgbuild_if_required(self, context: TransactionContext) -> Optional[str]:
|
||||||
build_only_chosen = context.config.get('aur_build_only_chosen')
|
build_only_chosen = context.config.get('aur_build_only_chosen')
|
||||||
|
|
||||||
|
|||||||
@@ -1002,10 +1002,6 @@ def get_packages_to_sync_first() -> Set[str]:
|
|||||||
return set()
|
return set()
|
||||||
|
|
||||||
|
|
||||||
def is_snapd_installed() -> bool:
|
|
||||||
return bool(run_cmd('pacman -Qq snapd', print_error=False))
|
|
||||||
|
|
||||||
|
|
||||||
def list_hard_requirements(name: str, logger: Optional[logging.Logger] = None,
|
def list_hard_requirements(name: str, logger: Optional[logging.Logger] = None,
|
||||||
assume_installed: Optional[Set[str]] = None) -> Optional[Set[str]]:
|
assume_installed: Optional[Set[str]] = None) -> Optional[Set[str]]:
|
||||||
cmd = StringIO()
|
cmd = StringIO()
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from bauh.api.paths import CONFIG_DIR, CACHE_DIR
|
|
||||||
from bauh.commons import resource
|
|
||||||
|
|
||||||
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
SNAP_CACHE_DIR = f'{CACHE_DIR}/snap'
|
|
||||||
CONFIG_FILE = f'{CONFIG_DIR}/snap.yml'
|
|
||||||
CATEGORIES_FILE_PATH = f'{SNAP_CACHE_DIR}/categories.txt'
|
|
||||||
URL_CATEGORIES_FILE = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/snap/categories.txt'
|
|
||||||
|
|
||||||
|
|
||||||
def get_icon_path() -> str:
|
|
||||||
return resource.get_path('img/snap.svg', ROOT_DIR)
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
from bauh.commons.config import YAMLConfigManager
|
|
||||||
from bauh.gems.snap import CONFIG_FILE
|
|
||||||
|
|
||||||
|
|
||||||
class SnapConfigManager(YAMLConfigManager):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(SnapConfigManager, self).__init__(config_file_path=CONFIG_FILE)
|
|
||||||
|
|
||||||
def get_default_config(self) -> dict:
|
|
||||||
return {'install_channel': False, 'categories_exp': 24}
|
|
||||||
@@ -1,573 +0,0 @@
|
|||||||
import re
|
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
from threading import Thread
|
|
||||||
from typing import List, Set, Type, Optional, Tuple, Generator
|
|
||||||
|
|
||||||
from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext, UpgradeRequirements, \
|
|
||||||
TransactionResult, SoftwareAction, SettingsView, SettingsController
|
|
||||||
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, PackageStatus
|
|
||||||
from bauh.api.abstract.view import SingleSelectComponent, SelectViewType, InputOption, PanelComponent, \
|
|
||||||
FormComponent, TextInputComponent
|
|
||||||
from bauh.api.exception import NoInternetException
|
|
||||||
from bauh.commons import suggestions
|
|
||||||
from bauh.commons.boot import CreateConfigFile
|
|
||||||
from bauh.commons.category import CategoriesDownloader
|
|
||||||
from bauh.commons.html import bold
|
|
||||||
from bauh.commons.system import SystemProcess, ProcessHandler, new_root_subprocess
|
|
||||||
from bauh.commons.view_utils import new_select, get_human_size_str
|
|
||||||
from bauh.gems.snap import snap, URL_CATEGORIES_FILE, CATEGORIES_FILE_PATH, \
|
|
||||||
get_icon_path, snapd
|
|
||||||
from bauh.gems.snap.config import SnapConfigManager
|
|
||||||
from bauh.gems.snap.model import SnapApplication
|
|
||||||
from bauh.gems.snap.snapd import SnapdClient
|
|
||||||
|
|
||||||
RE_AVAILABLE_CHANNELS = re.compile(re.compile(r'(\w+)\s+(snap install.+)'))
|
|
||||||
|
|
||||||
|
|
||||||
class SnapManager(SoftwareManager, SettingsController):
|
|
||||||
|
|
||||||
def __init__(self, context: ApplicationContext):
|
|
||||||
super(SnapManager, self).__init__(context=context)
|
|
||||||
self.i18n = context.i18n
|
|
||||||
self.api_cache = context.cache_factory.new()
|
|
||||||
context.disk_loader_factory.map(SnapApplication, self.api_cache)
|
|
||||||
self.enabled = True
|
|
||||||
self.http_client = context.http_client
|
|
||||||
self.logger = context.logger
|
|
||||||
self.ubuntu_distro = context.distro == 'ubuntu'
|
|
||||||
self.categories = {}
|
|
||||||
self.suggestions_cache = context.cache_factory.new()
|
|
||||||
self.info_path = None
|
|
||||||
self.configman = SnapConfigManager()
|
|
||||||
self._suggestions_url: Optional[str] = None
|
|
||||||
|
|
||||||
def _fill_categories(self, app: SnapApplication):
|
|
||||||
categories = self.categories.get(app.name.lower())
|
|
||||||
|
|
||||||
if categories:
|
|
||||||
app.categories = categories
|
|
||||||
|
|
||||||
if not app.is_application():
|
|
||||||
categories = app.categories
|
|
||||||
|
|
||||||
if categories is None:
|
|
||||||
categories = []
|
|
||||||
app.categories = categories
|
|
||||||
|
|
||||||
if 'runtime' not in categories:
|
|
||||||
categories.append('runtime')
|
|
||||||
|
|
||||||
def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult:
|
|
||||||
if is_url or (not snap.is_installed() and not snapd.is_running()):
|
|
||||||
return SearchResult([], [], 0)
|
|
||||||
|
|
||||||
snapd_client = SnapdClient(self.logger)
|
|
||||||
apps_found = snapd_client.query(words)
|
|
||||||
|
|
||||||
res = SearchResult([], [], 0)
|
|
||||||
|
|
||||||
if apps_found:
|
|
||||||
installed = self.read_installed(disk_loader).installed
|
|
||||||
|
|
||||||
for app_json in apps_found:
|
|
||||||
already_installed = None
|
|
||||||
|
|
||||||
if installed:
|
|
||||||
already_installed = [i for i in installed if i.id == app_json.get('id')]
|
|
||||||
already_installed = already_installed[0] if already_installed else None
|
|
||||||
|
|
||||||
if already_installed:
|
|
||||||
res.installed.append(already_installed)
|
|
||||||
else:
|
|
||||||
res.new.append(self._map_to_app(app_json, installed=False))
|
|
||||||
|
|
||||||
res.total = len(res.installed) + len(res.new)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def read_installed(self, disk_loader: DiskCacheLoader, limit: int = -1, only_apps: bool = False, pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = None) -> SearchResult:
|
|
||||||
if snap.is_installed() and snapd.is_running():
|
|
||||||
snapd_client = SnapdClient(self.logger)
|
|
||||||
app_names = {a['snap'] for a in snapd_client.list_only_apps()}
|
|
||||||
installed = [self._map_to_app(app_json=appjson,
|
|
||||||
installed=True,
|
|
||||||
disk_loader=disk_loader,
|
|
||||||
is_application=app_names and appjson['name'] in app_names) for appjson in snapd_client.list_all_snaps()]
|
|
||||||
return SearchResult(installed, None, len(installed))
|
|
||||||
else:
|
|
||||||
return SearchResult([], None, 0)
|
|
||||||
|
|
||||||
def downgrade(self, pkg: SnapApplication, root_password: Optional[str], watcher: ProcessWatcher) -> bool:
|
|
||||||
if not snap.is_installed():
|
|
||||||
watcher.print("'snap' seems not to be installed")
|
|
||||||
return False
|
|
||||||
if not snapd.is_running():
|
|
||||||
watcher.print("'snapd' seems not to be running")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return ProcessHandler(watcher).handle_simple(snap.downgrade_and_stream(pkg.name, root_password))[0]
|
|
||||||
|
|
||||||
def upgrade(self, requirements: UpgradeRequirements, root_password: Optional[str], watcher: ProcessWatcher) -> SystemProcess:
|
|
||||||
raise Exception(f"'upgrade' is not supported by {SnapManager.__class__.__name__}")
|
|
||||||
|
|
||||||
def uninstall(self, pkg: SnapApplication, root_password: Optional[str], watcher: ProcessWatcher, disk_loader: DiskCacheLoader) -> TransactionResult:
|
|
||||||
if snap.is_installed() and snapd.is_running():
|
|
||||||
uninstalled = ProcessHandler(watcher).handle_simple(snap.uninstall_and_stream(pkg.name, root_password))[0]
|
|
||||||
|
|
||||||
if uninstalled:
|
|
||||||
if self.suggestions_cache:
|
|
||||||
self.suggestions_cache.delete(pkg.name)
|
|
||||||
|
|
||||||
return TransactionResult(success=True, installed=None, removed=[pkg])
|
|
||||||
|
|
||||||
return TransactionResult.fail()
|
|
||||||
|
|
||||||
def get_managed_types(self) -> Set[Type[SoftwarePackage]]:
|
|
||||||
return {SnapApplication}
|
|
||||||
|
|
||||||
def clean_cache_for(self, pkg: SnapApplication):
|
|
||||||
super(SnapManager, self).clean_cache_for(pkg)
|
|
||||||
self.api_cache.delete(pkg.id)
|
|
||||||
|
|
||||||
def get_info(self, pkg: SnapApplication) -> dict:
|
|
||||||
info = {
|
|
||||||
'description': pkg.description,
|
|
||||||
'developer': pkg.developer,
|
|
||||||
'license': pkg.license,
|
|
||||||
'contact': pkg.contact,
|
|
||||||
'snap-id': pkg.id,
|
|
||||||
'name': pkg.name,
|
|
||||||
'publisher': pkg.publisher,
|
|
||||||
'revision': pkg.rev,
|
|
||||||
'tracking': pkg.tracking,
|
|
||||||
'channel': pkg.channel,
|
|
||||||
'type': pkg.type
|
|
||||||
}
|
|
||||||
|
|
||||||
if pkg.installed:
|
|
||||||
commands = [*{c['name'] for c in SnapdClient(self.logger).list_commands(pkg.name)}]
|
|
||||||
commands.sort()
|
|
||||||
info['commands'] = commands
|
|
||||||
|
|
||||||
if pkg.installed_size:
|
|
||||||
info['installed_size']: get_human_size_str(pkg.installed_size)
|
|
||||||
elif pkg.download_size:
|
|
||||||
info['download_size'] = get_human_size_str(pkg.download_size)
|
|
||||||
|
|
||||||
return info
|
|
||||||
|
|
||||||
def get_history(self, pkg: SnapApplication) -> PackageHistory:
|
|
||||||
raise Exception(f"'get_history' is not supported by {pkg.__class__.__name__}")
|
|
||||||
|
|
||||||
def install(self, pkg: SnapApplication, root_password: Optional[str], disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult:
|
|
||||||
# retrieving all installed so it will be possible to know the additional installed runtimes after the operation succeeds
|
|
||||||
if not snap.is_installed():
|
|
||||||
watcher.print("'snap' seems not to be installed")
|
|
||||||
return TransactionResult.fail()
|
|
||||||
|
|
||||||
if not snapd.is_running():
|
|
||||||
watcher.print("'snapd' seems not to be running")
|
|
||||||
return TransactionResult.fail()
|
|
||||||
|
|
||||||
installed_names = {s['name'] for s in SnapdClient(self.logger).list_all_snaps()}
|
|
||||||
|
|
||||||
client = SnapdClient(self.logger)
|
|
||||||
snap_config = self.configman.get_config()
|
|
||||||
|
|
||||||
try:
|
|
||||||
channel = self._request_channel_installation(pkg=pkg, snap_config=snap_config, snapd_client=client, watcher=watcher)
|
|
||||||
pkg.channel = channel
|
|
||||||
except Exception:
|
|
||||||
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
|
|
||||||
if 'not available on stable' in output:
|
|
||||||
channels = RE_AVAILABLE_CHANNELS.findall(output)
|
|
||||||
|
|
||||||
if channels:
|
|
||||||
opts = [InputOption(label=c[0], value=c[1]) for c in channels]
|
|
||||||
channel_select = SingleSelectComponent(type_=SelectViewType.RADIO, label='', options=opts, default_option=opts[0])
|
|
||||||
body = f"<p>{self.i18n['snap.install.available_channels.message'].format(bold(self.i18n['stable']), bold(pkg.name))}.</p>"
|
|
||||||
body += f"<p>{self.i18n['snap.install.available_channels.help']}:</p>"
|
|
||||||
|
|
||||||
if watcher.request_confirmation(title=self.i18n['snap.install.available_channels.title'],
|
|
||||||
body=body,
|
|
||||||
components=[channel_select],
|
|
||||||
confirmation_label=self.i18n['continue'],
|
|
||||||
deny_label=self.i18n['cancel']):
|
|
||||||
self.logger.info(f"Installing '{pkg.name}' with the custom command '{channel_select.value}'")
|
|
||||||
res = ProcessHandler(watcher).handle(SystemProcess(new_root_subprocess(channel_select.value.value.split(' '), root_password=root_password)))
|
|
||||||
return self._gen_installation_response(success=res, pkg=pkg,
|
|
||||||
installed=installed_names, disk_loader=disk_loader)
|
|
||||||
else:
|
|
||||||
self.logger.error(f"Could not find available channels in the installation output: {output}")
|
|
||||||
|
|
||||||
return self._gen_installation_response(success=res, pkg=pkg, installed=installed_names, disk_loader=disk_loader)
|
|
||||||
|
|
||||||
def _gen_installation_response(self, success: bool, pkg: SnapApplication, installed: Set[str], disk_loader: DiskCacheLoader):
|
|
||||||
if success:
|
|
||||||
new_installed = []
|
|
||||||
try:
|
|
||||||
net_available = self.context.internet_checker.is_available()
|
|
||||||
current_installed = self.read_installed(disk_loader=disk_loader, internet_available=net_available).installed
|
|
||||||
except Exception:
|
|
||||||
new_installed = [pkg]
|
|
||||||
traceback.print_exc()
|
|
||||||
current_installed = None
|
|
||||||
|
|
||||||
if current_installed:
|
|
||||||
for p in current_installed:
|
|
||||||
if p.name == pkg.name or (not installed or p.name not in installed):
|
|
||||||
new_installed.append(p)
|
|
||||||
|
|
||||||
return TransactionResult(success=success, installed=new_installed, removed=[])
|
|
||||||
else:
|
|
||||||
return TransactionResult.fail()
|
|
||||||
|
|
||||||
def is_enabled(self) -> bool:
|
|
||||||
return self.enabled
|
|
||||||
|
|
||||||
def set_enabled(self, enabled: bool):
|
|
||||||
self.enabled = enabled
|
|
||||||
|
|
||||||
def can_work(self) -> Tuple[bool, Optional[str]]:
|
|
||||||
return (True, None) if snap.is_installed() else (False, self.i18n['missing_dep'].format(dep=bold('snap')))
|
|
||||||
|
|
||||||
def requires_root(self, action: SoftwareAction, pkg: SnapApplication) -> bool:
|
|
||||||
return action not in (SoftwareAction.PREPARE, SoftwareAction.SEARCH)
|
|
||||||
|
|
||||||
def refresh(self, pkg: SnapApplication, root_password: Optional[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: Optional[str], watcher: ProcessWatcher) -> bool:
|
|
||||||
if not self.context.internet_checker.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 False
|
|
||||||
|
|
||||||
return ProcessHandler(watcher).handle_simple(snap.refresh_and_stream(app_name=pkg.name,
|
|
||||||
root_password=root_password,
|
|
||||||
channel=channel))[0]
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _start_category_task(self, taskman: TaskManager, create_config: CreateConfigFile, downloader: CategoriesDownloader):
|
|
||||||
if taskman:
|
|
||||||
taskman.update_progress('snap_cats', 0, self.i18n['task.waiting_task'].format(bold(create_config.task_name)))
|
|
||||||
create_config.join()
|
|
||||||
|
|
||||||
categories_exp = create_config.config['categories_exp']
|
|
||||||
downloader.expiration = categories_exp if isinstance(categories_exp, int) else None
|
|
||||||
taskman.update_progress('snap_cats', 1, None)
|
|
||||||
|
|
||||||
def _finish_category_task(self, taskman: TaskManager):
|
|
||||||
if taskman:
|
|
||||||
taskman.update_progress('snap_cats', 100, None)
|
|
||||||
taskman.finish_task('snap_cats')
|
|
||||||
|
|
||||||
def prepare(self, task_manager: TaskManager, root_password: Optional[str], internet_available: bool):
|
|
||||||
create_config = CreateConfigFile(taskman=task_manager, configman=self.configman, i18n=self.i18n,
|
|
||||||
task_icon_path=get_icon_path(), logger=self.logger)
|
|
||||||
create_config.start()
|
|
||||||
|
|
||||||
task_manager.register_task('snap_cats', self.i18n['task.download_categories'], get_icon_path())
|
|
||||||
category_downloader = CategoriesDownloader(id_='snap', manager=self, http_client=self.http_client,
|
|
||||||
logger=self.logger,
|
|
||||||
url_categories_file=URL_CATEGORIES_FILE,
|
|
||||||
categories_path=CATEGORIES_FILE_PATH,
|
|
||||||
internet_connection=internet_available,
|
|
||||||
internet_checker=self.context.internet_checker,
|
|
||||||
after=lambda: self._finish_category_task(task_manager))
|
|
||||||
category_downloader.before = lambda: self._start_category_task(task_manager, create_config, category_downloader)
|
|
||||||
category_downloader.start()
|
|
||||||
|
|
||||||
def list_updates(self, internet_available: bool) -> List[PackageUpdate]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def list_warnings(self, internet_available: bool) -> Optional[List[str]]:
|
|
||||||
if not snapd.is_running():
|
|
||||||
snap_bold = bold('Snap')
|
|
||||||
return [self.i18n['snap.notification.snapd_unavailable'].format(bold('snapd'), snap_bold),
|
|
||||||
self.i18n['snap.notification.snap.disable'].format(snap_bold,
|
|
||||||
bold(f"{self.i18n['settings'].capitalize()} > {self.i18n['core.config.tab.types']}"))]
|
|
||||||
elif internet_available:
|
|
||||||
available, output = snap.is_api_available()
|
|
||||||
|
|
||||||
if not available:
|
|
||||||
self.logger.warning(f'It seems Snap API is not available. Search output: {output}')
|
|
||||||
return [self.i18n['snap.notifications.api.unavailable'].format(bold('Snaps'), bold('Snap'))]
|
|
||||||
|
|
||||||
def _fill_suggestion(self, name: str, priority: SuggestionPriority, snapd_client: SnapdClient,
|
|
||||||
out: List[PackageSuggestion]):
|
|
||||||
res = snapd_client.find_by_name(name)
|
|
||||||
|
|
||||||
if res:
|
|
||||||
if len(res) == 1:
|
|
||||||
app_json = res[0]
|
|
||||||
else:
|
|
||||||
jsons_found = [p for p in res if p['name'] == name]
|
|
||||||
app_json = jsons_found[0] if jsons_found else None
|
|
||||||
|
|
||||||
if app_json:
|
|
||||||
sug = PackageSuggestion(self._map_to_app(app_json, False), priority)
|
|
||||||
self.suggestions_cache.add(name, sug)
|
|
||||||
out.append(sug)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.logger.warning(f"Could not retrieve suggestion '{name}'")
|
|
||||||
|
|
||||||
def _map_to_app(self, app_json: dict, installed: bool, disk_loader: Optional[DiskCacheLoader] = None, is_application: bool = False) -> SnapApplication:
|
|
||||||
app = SnapApplication(id=app_json.get('id'),
|
|
||||||
name=app_json.get('name'),
|
|
||||||
license=app_json.get('license'),
|
|
||||||
version=app_json.get('version'),
|
|
||||||
latest_version=app_json.get('version'),
|
|
||||||
description=app_json.get('description', app_json.get('summary')),
|
|
||||||
installed=installed,
|
|
||||||
rev=app_json.get('revision'),
|
|
||||||
publisher=app_json['publisher'].get('display-name', app_json['publisher'].get('username')),
|
|
||||||
verified_publisher=app_json['publisher'].get('validation') == 'verified',
|
|
||||||
icon_url=app_json.get('icon'),
|
|
||||||
screenshots={m['url'] for m in app_json.get('media', []) if m['type'] == 'screenshot'},
|
|
||||||
download_size=app_json.get('download-size'),
|
|
||||||
channel=app_json.get('channel'),
|
|
||||||
confinement=app_json.get('confinement'),
|
|
||||||
app_type=app_json.get('type'),
|
|
||||||
app=is_application,
|
|
||||||
installed_size=app_json.get('installed-size'))
|
|
||||||
|
|
||||||
if disk_loader and app.installed:
|
|
||||||
disk_loader.fill(app)
|
|
||||||
|
|
||||||
self._fill_categories(app)
|
|
||||||
|
|
||||||
app.status = PackageStatus.READY
|
|
||||||
return app
|
|
||||||
|
|
||||||
def _read_local_suggestions_file(self) -> Optional[str]:
|
|
||||||
try:
|
|
||||||
with open(self.suggestions_url) as f:
|
|
||||||
suggestions_str = f.read()
|
|
||||||
|
|
||||||
return suggestions_str
|
|
||||||
except FileNotFoundError:
|
|
||||||
self.logger.error(f"Local Snap suggestions file not found: {self.suggestions_url}")
|
|
||||||
except OSError:
|
|
||||||
self.logger.error(f"Could not read local Snap suggestions file: {self.suggestions_url}")
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
def _download_remote_suggestions_file(self) -> Optional[str]:
|
|
||||||
self.logger.info(f"Downloading the Snap suggestions from {self.suggestions_url}")
|
|
||||||
file = self.http_client.get(self.suggestions_url)
|
|
||||||
|
|
||||||
if file:
|
|
||||||
return file.text
|
|
||||||
|
|
||||||
def list_suggestions(self, limit: int, filter_installed: bool) -> Optional[List[PackageSuggestion]]:
|
|
||||||
if limit == 0 or not snapd.is_running():
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.is_local_suggestions_file_mapped():
|
|
||||||
suggestions_str = self._read_local_suggestions_file()
|
|
||||||
else:
|
|
||||||
suggestions_str = self._download_remote_suggestions_file()
|
|
||||||
|
|
||||||
if suggestions_str is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not suggestions_str:
|
|
||||||
self.logger.warning(f"No Snap suggestion found in {self.suggestions_url}")
|
|
||||||
return
|
|
||||||
|
|
||||||
ids_prios = suggestions.parse(suggestions_str, self.logger, 'Snap')
|
|
||||||
|
|
||||||
if not ids_prios:
|
|
||||||
self.logger.warning(f"No Snap suggestion could be parsed from {self.suggestions_url}")
|
|
||||||
return
|
|
||||||
|
|
||||||
suggestion_by_priority = suggestions.sort_by_priority(ids_prios)
|
|
||||||
snapd_client = SnapdClient(self.logger)
|
|
||||||
|
|
||||||
if filter_installed:
|
|
||||||
installed = {s['name'].lower() for s in snapd_client.list_all_snaps()}
|
|
||||||
|
|
||||||
if installed:
|
|
||||||
suggestion_by_priority = tuple(n for n in suggestion_by_priority if n not in installed)
|
|
||||||
|
|
||||||
if suggestion_by_priority and 0 < limit < len(suggestion_by_priority):
|
|
||||||
suggestion_by_priority = suggestion_by_priority[0:limit]
|
|
||||||
|
|
||||||
self.logger.info(f'Available Snap suggestions: {len(suggestion_by_priority)}')
|
|
||||||
|
|
||||||
if not suggestion_by_priority:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.logger.info("Mapping Snap suggestions")
|
|
||||||
|
|
||||||
instances, threads = [], []
|
|
||||||
|
|
||||||
res, cached_count = [], 0
|
|
||||||
for name in suggestion_by_priority:
|
|
||||||
cached_sug = self.suggestions_cache.get(name)
|
|
||||||
|
|
||||||
if cached_sug:
|
|
||||||
res.append(cached_sug)
|
|
||||||
cached_count += 1
|
|
||||||
else:
|
|
||||||
t = Thread(target=self._fill_suggestion, args=(name, ids_prios[name], snapd_client, res), daemon=True)
|
|
||||||
t.start()
|
|
||||||
threads.append(t)
|
|
||||||
time.sleep(0.001) # to avoid being blocked
|
|
||||||
|
|
||||||
for t in threads:
|
|
||||||
t.join()
|
|
||||||
|
|
||||||
if cached_count > 0:
|
|
||||||
self.logger.info(f"Returning {cached_count} cached Snap suggestions")
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
def is_default_enabled(self) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def launch(self, pkg: SnapApplication):
|
|
||||||
commands = SnapdClient(self.logger).list_commands(pkg.name)
|
|
||||||
|
|
||||||
if commands:
|
|
||||||
if len(commands) == 1:
|
|
||||||
cmd = commands[0]['name']
|
|
||||||
else:
|
|
||||||
desktop_cmd = [c for c in commands if 'desktop-file' in c]
|
|
||||||
|
|
||||||
if desktop_cmd:
|
|
||||||
cmd = desktop_cmd[0]['name']
|
|
||||||
else:
|
|
||||||
cmd = commands[0]['name']
|
|
||||||
|
|
||||||
self.logger.info(f"Running '{pkg.name}': {cmd}")
|
|
||||||
snap.run(cmd)
|
|
||||||
|
|
||||||
def get_screenshots(self, pkg: SnapApplication) -> Generator[str, None, None]:
|
|
||||||
if pkg.screenshots:
|
|
||||||
yield from pkg.screenshots
|
|
||||||
|
|
||||||
def get_settings(self) -> Optional[Generator[SettingsView, None, None]]:
|
|
||||||
snap_config = self.configman.get_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_='snap_install_channel',
|
|
||||||
tip=self.i18n['snap.config.install_channel.tip'])
|
|
||||||
|
|
||||||
cat_exp_val = snap_config['categories_exp'] if isinstance(snap_config['categories_exp'], int) else ''
|
|
||||||
categories_exp = TextInputComponent(id_='snap_cat_exp',
|
|
||||||
value=cat_exp_val,
|
|
||||||
only_int=True,
|
|
||||||
label=self.i18n['snap.config.categories_exp'],
|
|
||||||
tooltip=self.i18n['snap.config.categories_exp.tip'])
|
|
||||||
|
|
||||||
panel = PanelComponent([FormComponent([install_channel, categories_exp], self.i18n['installation'].capitalize())])
|
|
||||||
yield SettingsView(self, panel)
|
|
||||||
|
|
||||||
def save_settings(self, component: PanelComponent) -> Tuple[bool, Optional[List[str]]]:
|
|
||||||
config_ = self.configman.get_config()
|
|
||||||
|
|
||||||
form = component.get_component_by_idx(0, FormComponent)
|
|
||||||
config_['install_channel'] = form.get_component('snap_install_channel', SingleSelectComponent).get_selected()
|
|
||||||
config_['categories_exp'] = form.get_component('snap_cat_exp', TextInputComponent).get_int_value()
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.configman.save_config(config_)
|
|
||||||
return True, None
|
|
||||||
except Exception:
|
|
||||||
return False, [traceback.format_exc()]
|
|
||||||
|
|
||||||
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 Exception:
|
|
||||||
self.logger.warning(f"snapd client could not retrieve channels for '{pkg.name}'")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
self.logger.warning(f"snapd client could find a match for name '{pkg.name}' when retrieving its channels")
|
|
||||||
else:
|
|
||||||
if not data[0].get('channels'):
|
|
||||||
self.logger.info(f"No channel available for '{pkg.name}'. Skipping selection.")
|
|
||||||
else:
|
|
||||||
if pkg.channel:
|
|
||||||
current_channel = pkg.channel if '/' in pkg.channel else f'latest/{pkg.channel}'
|
|
||||||
else:
|
|
||||||
current_channel = f"latest/{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(f"No different channel available for '{pkg.name}'. Skipping selection.")
|
|
||||||
return
|
|
||||||
|
|
||||||
select = SingleSelectComponent(label='',
|
|
||||||
options=opts,
|
|
||||||
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'],
|
|
||||||
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()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def suggestions_url(self) -> str:
|
|
||||||
if not self._suggestions_url:
|
|
||||||
file_url = self.context.get_suggestion_url(self.__module__)
|
|
||||||
|
|
||||||
if not file_url:
|
|
||||||
file_url = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/snap/suggestions.txt'
|
|
||||||
|
|
||||||
self._suggestions_url = file_url
|
|
||||||
|
|
||||||
if file_url.startswith('/'):
|
|
||||||
self.logger.info(f"Local Snap suggestions file mapped: {file_url}")
|
|
||||||
|
|
||||||
return self._suggestions_url
|
|
||||||
|
|
||||||
def is_local_suggestions_file_mapped(self) -> bool:
|
|
||||||
return self.suggestions_url.startswith('/')
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
from typing import Optional, Set, Iterable, Tuple
|
|
||||||
|
|
||||||
from bauh.api.abstract.model import SoftwarePackage, CustomSoftwareAction
|
|
||||||
from bauh.commons import resource
|
|
||||||
from bauh.gems.snap import ROOT_DIR
|
|
||||||
|
|
||||||
|
|
||||||
class SnapApplication(SoftwarePackage):
|
|
||||||
|
|
||||||
__actions: Optional[Tuple[CustomSoftwareAction, CustomSoftwareAction]] = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def actions(cls) -> Tuple[CustomSoftwareAction, CustomSoftwareAction]:
|
|
||||||
if cls.__actions is None:
|
|
||||||
cls.__actions = (
|
|
||||||
CustomSoftwareAction(i18n_status_key='snap.action.refresh.status',
|
|
||||||
i18n_label_key='snap.action.refresh.label',
|
|
||||||
i18n_description_key='snap.action.refresh.desc',
|
|
||||||
icon_path=resource.get_path('img/refresh.svg', ROOT_DIR),
|
|
||||||
manager_method='refresh',
|
|
||||||
requires_root=True,
|
|
||||||
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',
|
|
||||||
i18n_description_key='snap.action.channel.desc',
|
|
||||||
icon_path=resource.get_path('img/refresh.svg', ROOT_DIR),
|
|
||||||
manager_method='change_channel',
|
|
||||||
requires_root=True,
|
|
||||||
requires_confirmation=False)
|
|
||||||
)
|
|
||||||
|
|
||||||
return cls.__actions
|
|
||||||
|
|
||||||
def __init__(self, id: str = None, name: str = None, version: str = None, latest_version: str = None,
|
|
||||||
description: str = None, publisher: str = None, rev: str = None, notes: str = None,
|
|
||||||
confinement: str = None, verified_publisher: bool = False,
|
|
||||||
screenshots: Optional[Set[str]] = None,
|
|
||||||
license: Optional[str] = None,
|
|
||||||
installed: bool = False,
|
|
||||||
icon_url: Optional[str] = None,
|
|
||||||
download_size: Optional[int] = None,
|
|
||||||
developer: Optional[str] = None,
|
|
||||||
contact: Optional[str] = None,
|
|
||||||
tracking: Optional[str] = None,
|
|
||||||
app_type: Optional[str] = None,
|
|
||||||
channel: Optional[str] = None,
|
|
||||||
app: bool = False,
|
|
||||||
installed_size: Optional[int] = None):
|
|
||||||
super(SnapApplication, self).__init__(id=id, name=name, version=version,
|
|
||||||
latest_version=latest_version, description=description,
|
|
||||||
license=license, installed=installed, icon_url=icon_url)
|
|
||||||
self.publisher = publisher
|
|
||||||
self.rev = rev
|
|
||||||
self.notes = notes
|
|
||||||
self.confinement = confinement
|
|
||||||
self.verified_publisher = verified_publisher
|
|
||||||
self.screenshots = screenshots
|
|
||||||
self.download_size = download_size
|
|
||||||
self.developer = developer
|
|
||||||
self.contact = contact
|
|
||||||
self.tracking = tracking
|
|
||||||
self.type = app_type
|
|
||||||
self.channel = channel
|
|
||||||
self.app = app
|
|
||||||
self.installed_size = installed_size
|
|
||||||
|
|
||||||
def supports_disk_cache(self):
|
|
||||||
return self.installed
|
|
||||||
|
|
||||||
def has_history(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def has_info(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def can_be_downgraded(self):
|
|
||||||
return self.installed
|
|
||||||
|
|
||||||
def get_type(self):
|
|
||||||
return 'snap'
|
|
||||||
|
|
||||||
def get_default_icon_path(self):
|
|
||||||
return resource.get_path('img/snap.svg', ROOT_DIR)
|
|
||||||
|
|
||||||
def get_type_icon_path(self):
|
|
||||||
return self.get_default_icon_path()
|
|
||||||
|
|
||||||
def is_application(self) -> bool:
|
|
||||||
if self.installed:
|
|
||||||
return self.app
|
|
||||||
else:
|
|
||||||
return self.type == 'app'
|
|
||||||
|
|
||||||
def get_disk_cache_path(self):
|
|
||||||
return f'{super(SnapApplication, self).get_disk_cache_path()}/installed/{self.name}'
|
|
||||||
|
|
||||||
def is_trustable(self) -> bool:
|
|
||||||
return self.verified_publisher
|
|
||||||
|
|
||||||
def get_data_to_cache(self):
|
|
||||||
return {
|
|
||||||
'categories': self.categories
|
|
||||||
}
|
|
||||||
|
|
||||||
def fill_cached_data(self, data: dict):
|
|
||||||
if data:
|
|
||||||
for base_attr in self.get_data_to_cache().keys():
|
|
||||||
if data.get(base_attr):
|
|
||||||
setattr(self, base_attr, data[base_attr])
|
|
||||||
|
|
||||||
def can_be_run(self) -> bool:
|
|
||||||
return bool(self.installed and self.is_application())
|
|
||||||
|
|
||||||
def get_publisher(self):
|
|
||||||
return self.publisher
|
|
||||||
|
|
||||||
def get_custom_actions(self) -> Optional[Iterable[CustomSoftwareAction]]:
|
|
||||||
if self.installed:
|
|
||||||
return self.actions()
|
|
||||||
|
|
||||||
def supports_backup(self) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def has_screenshots(self) -> bool:
|
|
||||||
return not self.installed and self.screenshots
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if isinstance(other, SnapApplication):
|
|
||||||
return self.name == other.name
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
version="1.1"
|
|
||||||
id="Capa_1"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
width="512"
|
|
||||||
height="512"
|
|
||||||
viewBox="0 0 511.99998 512"
|
|
||||||
xml:space="preserve"
|
|
||||||
sodipodi:docname="refresh.svg"
|
|
||||||
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"><metadata
|
|
||||||
id="metadata45"><rdf:RDF><cc:Work
|
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
|
|
||||||
id="defs43" /><sodipodi:namedview
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="1366"
|
|
||||||
inkscape:window-height="739"
|
|
||||||
id="namedview41"
|
|
||||||
showgrid="false"
|
|
||||||
showborder="true"
|
|
||||||
inkscape:zoom="0.48437083"
|
|
||||||
inkscape:cx="133.71431"
|
|
||||||
inkscape:cy="289.92474"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="Capa_1"
|
|
||||||
fit-margin-top="0"
|
|
||||||
fit-margin-left="0"
|
|
||||||
fit-margin-right="0"
|
|
||||||
fit-margin-bottom="0"
|
|
||||||
inkscape:document-rotation="0" />
|
|
||||||
<g
|
|
||||||
id="g8"
|
|
||||||
transform="matrix(1.0938971,0,0,1.0217723,-10.489433,7.0809411)"
|
|
||||||
style="fill:#3991e9;fill-opacity:1;stroke:none;stroke-width:39.2413;stroke-miterlimit:4;stroke-dasharray:none">
|
|
||||||
<g
|
|
||||||
id="g6"
|
|
||||||
style="fill:#3991e9;fill-opacity:1;stroke:none;stroke-width:39.2413;stroke-miterlimit:4;stroke-dasharray:none">
|
|
||||||
<path
|
|
||||||
d="m 55.323,203.641 c 15.664,0 29.813,-9.405 35.872,-23.854 25.017,-59.604 83.842,-101.61 152.42,-101.61 37.797,0 72.449,12.955 100.23,34.442 l -21.775,3.371 c -7.438,1.153 -13.224,7.054 -14.232,14.512 -1.01,7.454 3.008,14.686 9.867,17.768 l 119.746,53.872 c 5.249,2.357 11.33,1.904 16.168,-1.205 4.83,-3.114 7.764,-8.458 7.796,-14.208 l 0.621,-131.943 c 0.042,-7.506 -4.851,-14.144 -12.024,-16.332 -7.185,-2.188 -14.947,0.589 -19.104,6.837 L 414.403,70.096 C 370.398,26.778 310.1,0 243.615,0 142.806,0 56.133,61.562 19.167,149.06 c -5.134,12.128 -3.84,26.015 3.429,36.987 7.269,10.976 19.556,17.594 32.727,17.594 z"
|
|
||||||
id="path2"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#3991e9;fill-opacity:1;stroke:none;stroke-width:39.2413;stroke-miterlimit:4;stroke-dasharray:none" />
|
|
||||||
<path
|
|
||||||
d="m 464.635,301.184 c -7.27,-10.977 -19.558,-17.594 -32.728,-17.594 -15.664,0 -29.813,9.405 -35.872,23.854 -25.018,59.604 -83.843,101.61 -152.42,101.61 -37.798,0 -72.45,-12.955 -100.232,-34.442 l 21.776,-3.369 c 7.437,-1.153 13.223,-7.055 14.233,-14.514 1.009,-7.453 -3.008,-14.686 -9.867,-17.768 L 49.779,285.089 c -5.25,-2.356 -11.33,-1.905 -16.169,1.205 -4.829,3.114 -7.764,8.458 -7.795,14.207 l -0.622,131.943 c -0.042,7.506 4.85,14.144 12.024,16.332 7.185,2.188 14.948,-0.59 19.104,-6.839 l 16.505,-24.805 c 44.004,43.32 104.303,70.098 170.788,70.098 100.811,0 187.481,-61.561 224.446,-149.059 5.137,-12.128 3.843,-26.014 -3.425,-36.987 z"
|
|
||||||
id="path4"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#3991e9;fill-opacity:1;stroke:none;stroke-width:39.2413;stroke-miterlimit:4;stroke-dasharray:none" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g10"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g12"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g14"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g16"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g18"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g20"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g22"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g24"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g26"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g28"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g30"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g32"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g34"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g36"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g38"
|
|
||||||
transform="translate(-15.027876,-464.19647)">
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.5 KiB |
@@ -1,70 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
id="svg3957"
|
|
||||||
height="6.3499999mm"
|
|
||||||
width="6.3499999mm"
|
|
||||||
version="1.1"
|
|
||||||
viewBox="0 0 24.000124 24.000125"
|
|
||||||
sodipodi:docname="snap.svg"
|
|
||||||
inkscape:version="0.92.4 5da689c313, 2019-01-14">
|
|
||||||
<defs
|
|
||||||
id="defs83" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="1360"
|
|
||||||
inkscape:window-height="703"
|
|
||||||
id="namedview81"
|
|
||||||
showgrid="false"
|
|
||||||
showborder="false"
|
|
||||||
fit-margin-top="0"
|
|
||||||
fit-margin-left="0"
|
|
||||||
fit-margin-right="0"
|
|
||||||
fit-margin-bottom="0"
|
|
||||||
inkscape:zoom="1.6803366"
|
|
||||||
inkscape:cx="192.32571"
|
|
||||||
inkscape:cy="-49.135216"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="svg3957" />
|
|
||||||
<metadata
|
|
||||||
id="metadata3963">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title></dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
style="fill:none"
|
|
||||||
id="snapcraft-logo--web"
|
|
||||||
transform="matrix(0.76433516,0,0,0.80000413,0,-0.40000206)">
|
|
||||||
<path
|
|
||||||
id="Combined-Shape"
|
|
||||||
d="m 18.06,7.28 6.92,3.08 -6.92,6.92 z M 4.84,30.5 l 8.49,-15.92 3.73,3.7 z M 0,0.5 17.47,6.82 v 11.05 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#82bfa1" />
|
|
||||||
<polygon
|
|
||||||
id="Shape"
|
|
||||||
points="18.46,6.82 31.4,12.57 28.53,6.82 "
|
|
||||||
style="fill:#fa6340" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB |
@@ -1,34 +0,0 @@
|
|||||||
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.desc=Allows to change the application channel (if available)
|
|
||||||
snap.action.channel.status=Changing channel of
|
|
||||||
snap.action.refresh.confirm=Actualitza {} ?
|
|
||||||
snap.action.refresh.desc=Updates the application for the latest version
|
|
||||||
snap.action.refresh.label=Actualitza
|
|
||||||
snap.action.refresh.status=S’està actualitzant
|
|
||||||
snap.config.categories_exp=Categories expiration
|
|
||||||
snap.config.categories_exp.tip=It defines the expiration time (in HOURS) of the Snaps categories mapping file stored in disc. Use 0 so that it is always updated during initialization.
|
|
||||||
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
|
|
||||||
snap.info.description=descripció
|
|
||||||
snap.info.download_size=Size (Download)
|
|
||||||
snap.info.installed=instal·lada
|
|
||||||
snap.info.installed_size=Size (Instalattion)
|
|
||||||
snap.info.license=llicència
|
|
||||||
snap.info.publisher=publicador
|
|
||||||
snap.info.revision=revisió
|
|
||||||
snap.info.tracking=seguiment
|
|
||||||
snap.info.type=type
|
|
||||||
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.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
gem.snap.info=Auf https://snapcraft.io/store veröffentlichte Anwendungen
|
|
||||||
snap.action.channel.confirm=Kanal von {} ändern?
|
|
||||||
snap.action.channel.error.no_channel=Kein verfügbarer Kanal für {}
|
|
||||||
snap.action.channel.label=Kanal ändern
|
|
||||||
snap.action.channel.desc=Ermöglicht das Ändern des Anwendungskanals (falls verfügbar)
|
|
||||||
snap.action.channel.status=Ändern des Kanals von
|
|
||||||
snap.action.refresh.confirm=Aktualisieren {} ?
|
|
||||||
snap.action.refresh.desc=Aktualisiert die Anwendung auf die neueste Version
|
|
||||||
snap.action.refresh.label=Aktualisieren
|
|
||||||
snap.action.refresh.status=Aktualisieren
|
|
||||||
snap.config.categories_exp=Gültigkeitszeitraum der Kategorien
|
|
||||||
snap.config.categories_exp.tip=Legt den Gültigkeitszeitraum (in Stunden) der auf dem Datenträger gespeicherten Kategorien-Zuordnungsdatei der Snaps fest. Verwenden Sie 0, damit die Datei während der Initialisierung immer aktualisiert wird.
|
|
||||||
snap.config.install_channel=Kanäle anzeigen
|
|
||||||
snap.config.install_channel.tip=Hier können Sie einen der verfügbaren Kanäle für die zu installierende Anwendung auswählen
|
|
||||||
snap.info.channel=Kanal
|
|
||||||
snap.info.commands=Befehle
|
|
||||||
snap.info.contact=Kontakt
|
|
||||||
snap.info.description=Beschreibung
|
|
||||||
snap.info.download_size=Größe (Download)
|
|
||||||
snap.info.installed=installiert
|
|
||||||
snap.info.installed_size=Größe (Installation)
|
|
||||||
snap.info.license=Lizenz
|
|
||||||
snap.info.publisher=Herausgeber
|
|
||||||
snap.info.revision=Revision
|
|
||||||
snap.info.tracking=Verfolgung
|
|
||||||
snap.info.type=Typ
|
|
||||||
snap.info.version=Version
|
|
||||||
snap.install.available_channels.help=Wählen Sie eine aus, wenn Sie fortfahren möchten
|
|
||||||
snap.install.available_channels.message=Es ist kein {} Kanal für {} verfügbar. Aber es gibt diese untenstehenden
|
|
||||||
snap.install.available_channels.title=Verfügbare Kanäle
|
|
||||||
snap.install.channel.body=Wählen Sie einen Kanal, um fortzufahren
|
|
||||||
snap.notification.snap.disable=Wenn Sie keine Snap-Anwendungen verwenden möchten, deaktivieren Sie {} in {}
|
|
||||||
snap.notification.snapd_unavailable={} scheint nicht gestartet oder aktiviert zu sein. {} Pakete werden nicht verfügbar sein.
|
|
||||||
snap.notifications.api.unavailable=Es scheint, dass die {} API im Moment nicht verfügbar ist. Es wird nicht möglich sein, nach neuen {} Anwendungen zu suchen.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
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.desc=Allows to change the application channel (if available)
|
|
||||||
snap.action.channel.status=Changing channel of
|
|
||||||
snap.action.refresh.confirm=Refresh {}?
|
|
||||||
snap.action.refresh.desc=Updates the application for the latest version
|
|
||||||
snap.action.refresh.label=Refresh
|
|
||||||
snap.action.refresh.status=Refreshing
|
|
||||||
snap.config.categories_exp=Categories expiration
|
|
||||||
snap.config.categories_exp.tip=It defines the expiration time (in HOURS) of the Snaps categories mapping file stored in disc. Use 0 so that it is always updated during initialization.
|
|
||||||
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
|
|
||||||
snap.info.description=description
|
|
||||||
snap.info.download_size=Size (Download)
|
|
||||||
snap.info.installed=installed
|
|
||||||
snap.info.installed_size=Size (Installation)
|
|
||||||
snap.info.license=license
|
|
||||||
snap.info.publisher=publisher
|
|
||||||
snap.info.revision=revision
|
|
||||||
snap.info.tracking=tracking
|
|
||||||
snap.info.type=type
|
|
||||||
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.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
gem.snap.info=Aplicativos publicados en https://snapcraft.io/store
|
|
||||||
snap.action.channel.confirm=Cambiar canal de {}?
|
|
||||||
snap.action.channel.desc=Permite cambiar el canal de la aplicación (si disponible)
|
|
||||||
snap.action.channel.error.no_channel=No hay canal disponible para {}
|
|
||||||
snap.action.channel.status=Cambiando canal de
|
|
||||||
snap.action.channel.label=Cambiar canal
|
|
||||||
snap.action.refresh.confirm=Actualizar {} ?
|
|
||||||
snap.action.refresh.desc=Actualiza la aplicación para la versión más reciente
|
|
||||||
snap.action.refresh.label=Actualizar
|
|
||||||
snap.action.refresh.status=Actualizando
|
|
||||||
snap.config.categories_exp=Expiración de categorías
|
|
||||||
snap.config.categories_exp.tip=Define el tiempo de vencimiento (en HORAS) del archivo de mapeo de categorías de Snaps almacenado en el disco. Utilice 0 para que siempre se actualice durante la inicialización.
|
|
||||||
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
|
|
||||||
snap.info.description=descripción
|
|
||||||
snap.info.download_size=Tamaño (Descarga)
|
|
||||||
snap.info.installed=instalado
|
|
||||||
snap.info.installed_size=Tamaño (Instalación)
|
|
||||||
snap.info.license=licencia
|
|
||||||
snap.info.publisher=publicador
|
|
||||||
snap.info.revision=revisión
|
|
||||||
snap.info.tracking=tracking
|
|
||||||
snap.info.type=tipo
|
|
||||||
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 {}.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
gem.snap.info=Applications publiées à https://snapcraft.io/store
|
|
||||||
snap.action.channel.confirm=Changer le canal de {} ?
|
|
||||||
snap.action.channel.desc=Allows to change the application channel (if available)
|
|
||||||
snap.action.channel.error.no_channel=Aucun canal disponible pour {}
|
|
||||||
snap.action.channel.label=Changer de canal
|
|
||||||
snap.action.channel.status=Changement de canal de
|
|
||||||
snap.action.refresh.confirm=Actualiser {} ?
|
|
||||||
snap.action.refresh.desc=Updates the application for the latest version
|
|
||||||
snap.action.refresh.label=Actualiser
|
|
||||||
snap.action.refresh.status=Actualisation
|
|
||||||
snap.config.categories_exp=Categories expiration
|
|
||||||
snap.config.categories_exp.tip=It defines the expiration time (in HOURS) of the Snaps categories mapping file stored in disc. Use 0 so that it is always updated during initialization.
|
|
||||||
snap.config.install_channel=Afficher les canaux
|
|
||||||
snap.config.install_channel.tip=Permet de sélectionner un des canaux disponibles pour l'application à installer
|
|
||||||
snap.info.channel=canal
|
|
||||||
snap.info.commands=commandes
|
|
||||||
snap.info.contact=contact
|
|
||||||
snap.info.description=description
|
|
||||||
snap.info.download_size=Taille (Téléchargement)
|
|
||||||
snap.info.installed=installé
|
|
||||||
snap.info.installed_size=Size (Installation)
|
|
||||||
snap.info.license=licence
|
|
||||||
snap.info.publisher=éditeur
|
|
||||||
snap.info.revision=révision
|
|
||||||
snap.info.tracking=pisteur
|
|
||||||
snap.info.type=type
|
|
||||||
snap.info.version=version
|
|
||||||
snap.install.available_channels.help=Selectionnez pour continuer
|
|
||||||
snap.install.available_channels.message=Pas de canal {} disponible pour {}. Mais les suivants existent
|
|
||||||
snap.install.available_channels.title=Canaux disponibles
|
|
||||||
snap.install.channel.body=Choisissez un canal pour continuer
|
|
||||||
snap.notification.snap.disable=Si vous ne voulez pas utiliser des applications Snap, décochez {} dans {}
|
|
||||||
snap.notification.snapd_unavailable={} a l'air de n'être ni démarré ni activé. Les paquets {} ne seront pas disponibles.
|
|
||||||
snap.notifications.api.unavailable=L'API {} a l'air d'être momentanément indisponible. Impossible de chercher de nouvelles applications {}.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
gem.snap.info=Applicazioni pubblicate su https://snapcraft.io/store
|
|
||||||
snap.action.channel.confirm=Cambia canale di {} ?
|
|
||||||
snap.action.channel.desc=Allows to change the application channel (if available)
|
|
||||||
snap.action.channel.error.no_channel=Nessun canale disponibile per {}
|
|
||||||
snap.action.channel.label=Cambia canale
|
|
||||||
snap.action.channel.status=Cambiare canale di
|
|
||||||
snap.action.refresh.confirm=Ripristina {} ?
|
|
||||||
snap.action.refresh.desc=Updates the application for the latest version
|
|
||||||
snap.action.refresh.label=Ripristina
|
|
||||||
snap.action.refresh.status=Ripristinare
|
|
||||||
snap.config.categories_exp=Scadenza delle categorie
|
|
||||||
snap.config.categories_exp.tip=Definisce il tempo di scadenza (in ORE) del file di mappatura delle categorie di Snap archiviato nel disco. Utilizzare 0 in modo che venga sempre aggiornato durante l'inizializzazione.
|
|
||||||
snap.config.install_channel=Visualizza i canali
|
|
||||||
snap.config.install_channel.tip=Permette di selezionare uno dei canali disponibili per la data applicazione da installare
|
|
||||||
snap.info.channel=canale
|
|
||||||
snap.info.commands=comandi
|
|
||||||
snap.info.contact=contatto
|
|
||||||
snap.info.description=descrizione
|
|
||||||
snap.info.download_size=dimensione (Download)
|
|
||||||
snap.info.installed=installato
|
|
||||||
snap.info.installed_size=Dimensione (Installazione)
|
|
||||||
snap.info.license=licenza
|
|
||||||
snap.info.publisher=editore
|
|
||||||
snap.info.revision=revisione
|
|
||||||
snap.info.tracking=tracking
|
|
||||||
snap.info.type=tipo
|
|
||||||
snap.info.version=versione
|
|
||||||
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=Seleziona un canale per procedere
|
|
||||||
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.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
gem.snap.info=Aplicativos publicados em https://snapcraft.io/store
|
|
||||||
snap.action.channel.confirm=Alterar canal de {} ?
|
|
||||||
snap.action.channel.desc=Permite trocar o canal do aplicativo (se disponível)
|
|
||||||
snap.action.channel.error.no_channel=Nenhum canal disponível para {}
|
|
||||||
snap.action.channel.label=Alterar canal
|
|
||||||
snap.action.channel.status=Alterando canal de
|
|
||||||
snap.action.refresh.confirm=Atualizar {} ?
|
|
||||||
snap.action.refresh.desc=Atualiza o aplicativo para a versão mais recente
|
|
||||||
snap.action.refresh.label=Atualizar
|
|
||||||
snap.action.refresh.status=Atualizando
|
|
||||||
snap.config.categories_exp=Expiração de categorias
|
|
||||||
snap.config.categories_exp.tip=Define o tempo de expiração (em HORAS) do arquivo de mapeamento de categorias Snap armazenado em disco. Utilize 0 para que ele seja sempre atualizado durante a inicialização.
|
|
||||||
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
|
|
||||||
snap.info.description=descrição
|
|
||||||
snap.info.download_size=Tamanho (Download)
|
|
||||||
snap.info.installed=instalado
|
|
||||||
snap.info.installed_size=Tamanho (Instalação)
|
|
||||||
snap.info.license=licença
|
|
||||||
snap.info.publisher=publicador
|
|
||||||
snap.info.revision=revisão
|
|
||||||
snap.info.tracking=tracking
|
|
||||||
snap.info.type=tipo
|
|
||||||
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 {}.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
gem.snap.info=Приложения, опубликованные на https://snapcraft.io/store
|
|
||||||
snap.action.channel.confirm=Изменить канал на {} ?
|
|
||||||
snap.action.channel.desc=Позволяет изменить канал приложения (если он доступен)
|
|
||||||
snap.action.channel.error.no_channel=Нет доступного канала для {}
|
|
||||||
snap.action.channel.label=Изменить канал
|
|
||||||
snap.action.channel.status=Изменение канала на
|
|
||||||
snap.action.refresh.confirm=Обновить {} ?
|
|
||||||
snap.action.refresh.desc=Обновление приложения до последней версии
|
|
||||||
snap.action.refresh.label=Обновить
|
|
||||||
snap.action.refresh.status=Обновляется
|
|
||||||
snap.config.categories_exp=Срок действия категорий
|
|
||||||
snap.config.categories_exp.tip=Определяет время действия (в часах) файла отображения категорий Snaps, хранящегося на диске. Используйте 0, чтобы он всегда обновлялся при инициализации.
|
|
||||||
snap.config.install_channel=Отображение каналов
|
|
||||||
snap.config.install_channel.tip=Позволяет выбрать один из доступных каналов для устанавливаемого приложения
|
|
||||||
snap.info.channel=Канал
|
|
||||||
snap.info.commands=Команды
|
|
||||||
snap.info.contact=Контакт
|
|
||||||
snap.info.description=Описание
|
|
||||||
snap.info.download_size=Размер (Загрузки)
|
|
||||||
snap.info.installed=Размер установки
|
|
||||||
snap.info.installed_size=Размер (Установки)
|
|
||||||
snap.info.license=Лицензия
|
|
||||||
snap.info.publisher=Издатель
|
|
||||||
snap.info.revision=Ревизия
|
|
||||||
snap.info.tracking=Отслеживание
|
|
||||||
snap.info.type=тип
|
|
||||||
snap.info.version=Версия
|
|
||||||
snap.install.available_channels.help=Выберите один, если вы хотите продолжить
|
|
||||||
snap.install.available_channels.message=Нет одного канала {}, доступного для {}. Но есть такие ниже
|
|
||||||
snap.install.available_channels.title=Доступные каналы
|
|
||||||
snap.install.channel.body=Выберите канал для продолжения операции
|
|
||||||
snap.notification.snap.disable=Если вы не хотите использовать приложения Snap, снимите флажок {} в {}
|
|
||||||
snap.notification.snapd_unavailable={} кажется, не запускается и не включается. {} пакеты будут недоступны.
|
|
||||||
snap.notifications.api.unavailable=Похоже, что API {} в данный момент недоступен. Невозможно будет искать новые приложения {}.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
gem.snap.info=https://snapcraft.io/store adresinde yayınlanan uygulamalar
|
|
||||||
snap.action.channel.confirm=Change channel of {} ?
|
|
||||||
snap.action.channel.desc=Allows to change the application channel (if available)
|
|
||||||
snap.action.channel.error.no_channel=No available channel for {}
|
|
||||||
snap.action.channel.label=Change channel
|
|
||||||
snap.action.channel.status=Changing channel of
|
|
||||||
snap.action.refresh.confirm=Yenile {} ?
|
|
||||||
snap.action.refresh.desc=Updates the application for the latest version
|
|
||||||
snap.action.refresh.label=Yenile
|
|
||||||
snap.action.refresh.status=Yenileniyor
|
|
||||||
snap.config.categories_exp=Categories expiration
|
|
||||||
snap.config.categories_exp.tip=It defines the expiration time (in HOURS) of the Snaps categories mapping file stored in disc. Use 0 so that it is always updated during initialization.
|
|
||||||
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
|
|
||||||
snap.info.description=açıklama
|
|
||||||
snap.info.download_size=Size (Download)
|
|
||||||
snap.info.installed=yüklü
|
|
||||||
snap.info.installed_size=Size (Instalattion)
|
|
||||||
snap.info.license=lisans
|
|
||||||
snap.info.publisher=yayıncı
|
|
||||||
snap.info.revision=revizyon
|
|
||||||
snap.info.tracking=izleme
|
|
||||||
snap.info.type=type
|
|
||||||
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.
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
gem.snap.info=在 https://snapcraft.io/store 上发布的应用程序
|
|
||||||
snap.action.channel.confirm=更改 {} 的频道?
|
|
||||||
snap.action.channel.error.no_channel=没有可用于 {} 的频道
|
|
||||||
snap.action.channel.label=更改频道
|
|
||||||
snap.action.channel.desc=允许更改应用程序的频道(如果可用)
|
|
||||||
snap.action.channel.status=更改
|
|
||||||
snap.action.refresh.confirm=刷新 {}?
|
|
||||||
snap.action.refresh.desc=将应用程序更新到最新版本
|
|
||||||
snap.action.refresh.label=刷新
|
|
||||||
snap.action.refresh.status=刷新
|
|
||||||
snap.config.categories_exp=类别过期时间
|
|
||||||
snap.config.categories_exp.tip=它定义了 Snap 分类映射文件在磁盘上存储的过期时间(以小时为单位)。在初始化期间始终更新,使用 0。
|
|
||||||
snap.config.install_channel=显示频道
|
|
||||||
snap.config.install_channel.tip=允许为给定应用程序选择可用频道进行安装
|
|
||||||
snap.info.channel=频道
|
|
||||||
snap.info.commands=命令
|
|
||||||
snap.info.contact=联系
|
|
||||||
snap.info.description=描述
|
|
||||||
snap.info.download_size=大小(下载)
|
|
||||||
snap.info.installed=已安装
|
|
||||||
snap.info.installed_size=大小(安装)
|
|
||||||
snap.info.license=许可证
|
|
||||||
snap.info.publisher=发布者
|
|
||||||
snap.info.revision=修订版
|
|
||||||
snap.info.tracking=追踪
|
|
||||||
snap.info.type=类型
|
|
||||||
snap.info.version=版本
|
|
||||||
snap.install.available_channels.help=如果要继续,请选择一个
|
|
||||||
snap.install.available_channels.message=没有 {} 的可用频道供 {} 使用。但下面有这些
|
|
||||||
snap.install.available_channels.title=可用频道
|
|
||||||
snap.install.channel.body=选择一个频道以继续
|
|
||||||
snap.notification.snap.disable=如果您不想使用 Snap 应用程序,请取消选中 {}
|
|
||||||
snap.notification.snapd_unavailable={} 似乎没有启动或启用。{} 包将不可用。
|
|
||||||
snap.notifications.api.unavailable=目前似乎 {} API 不可用。将无法搜索新的 {} 应用程序。
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import os
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
from io import StringIO
|
|
||||||
from typing import Tuple, Optional
|
|
||||||
|
|
||||||
from bauh.commons.system import SimpleProcess
|
|
||||||
|
|
||||||
|
|
||||||
def is_installed() -> bool:
|
|
||||||
return bool(shutil.which('snap'))
|
|
||||||
|
|
||||||
|
|
||||||
def uninstall_and_stream(app_name: str, root_password: Optional[str]) -> SimpleProcess:
|
|
||||||
return SimpleProcess(cmd=('snap', 'remove', app_name),
|
|
||||||
root_password=root_password,
|
|
||||||
lang=None,
|
|
||||||
shell=True)
|
|
||||||
|
|
||||||
|
|
||||||
def install_and_stream(app_name: str, confinement: str, root_password: Optional[str], channel: Optional[str] = None) -> SimpleProcess:
|
|
||||||
|
|
||||||
install_cmd = ['snap', 'install', app_name] # default
|
|
||||||
|
|
||||||
if confinement == 'classic':
|
|
||||||
install_cmd.append('--classic')
|
|
||||||
|
|
||||||
if channel:
|
|
||||||
install_cmd.append(f'--channel={channel}')
|
|
||||||
|
|
||||||
return SimpleProcess(install_cmd, root_password=root_password, shell=True, lang=None)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade_and_stream(app_name: str, root_password: Optional[str]) -> SimpleProcess:
|
|
||||||
return SimpleProcess(cmd=('snap', 'revert', app_name),
|
|
||||||
root_password=root_password,
|
|
||||||
shell=True,
|
|
||||||
lang=None)
|
|
||||||
|
|
||||||
|
|
||||||
def refresh_and_stream(app_name: str, root_password: Optional[str], channel: Optional[str] = None) -> SimpleProcess:
|
|
||||||
cmd = ['snap', 'refresh', app_name]
|
|
||||||
|
|
||||||
if channel:
|
|
||||||
cmd.append(f'--channel={channel}')
|
|
||||||
|
|
||||||
return SimpleProcess(cmd=cmd,
|
|
||||||
root_password=root_password,
|
|
||||||
lang=None,
|
|
||||||
shell=True)
|
|
||||||
|
|
||||||
|
|
||||||
def run(cmd: str):
|
|
||||||
subprocess.Popen((f'snap run {cmd}',), shell=True, env={**os.environ})
|
|
||||||
|
|
||||||
|
|
||||||
def is_api_available() -> Tuple[bool, str]:
|
|
||||||
output = StringIO()
|
|
||||||
for o in SimpleProcess(('snap', 'search'), lang=None).instance.stdout:
|
|
||||||
if o:
|
|
||||||
output.write(o.decode())
|
|
||||||
|
|
||||||
output.seek(0)
|
|
||||||
output = output.read()
|
|
||||||
return 'error:' not in output, output
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
import socket
|
|
||||||
import traceback
|
|
||||||
from logging import Logger
|
|
||||||
from typing import Optional, List
|
|
||||||
|
|
||||||
from requests import Session
|
|
||||||
from requests.adapters import HTTPAdapter
|
|
||||||
from urllib3.connection import HTTPConnection
|
|
||||||
from urllib3.connectionpool import HTTPConnectionPool
|
|
||||||
|
|
||||||
from bauh.commons.system import run_cmd
|
|
||||||
|
|
||||||
URL_BASE = 'http://snapd/v2'
|
|
||||||
|
|
||||||
|
|
||||||
class SnapdConnection(HTTPConnection):
|
|
||||||
def __init__(self):
|
|
||||||
super(SnapdConnection, self).__init__('localhost')
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
||||||
self.sock.connect("/run/snapd.socket")
|
|
||||||
|
|
||||||
|
|
||||||
class SnapdConnectionPool(HTTPConnectionPool):
|
|
||||||
def __init__(self):
|
|
||||||
super(SnapdConnectionPool, self).__init__('localhost')
|
|
||||||
|
|
||||||
def _new_conn(self):
|
|
||||||
return SnapdConnection()
|
|
||||||
|
|
||||||
|
|
||||||
class SnapdAdapter(HTTPAdapter):
|
|
||||||
|
|
||||||
def get_connection(self, url, proxies=None):
|
|
||||||
return SnapdConnectionPool()
|
|
||||||
|
|
||||||
|
|
||||||
class SnapdClient:
|
|
||||||
|
|
||||||
def __init__(self, logger: Logger):
|
|
||||||
self.logger = logger
|
|
||||||
self.session = self._new_session()
|
|
||||||
|
|
||||||
def _new_session(self) -> Optional[Session]:
|
|
||||||
try:
|
|
||||||
session = Session()
|
|
||||||
session.mount("http://snapd/", SnapdAdapter())
|
|
||||||
return session
|
|
||||||
except Exception:
|
|
||||||
self.logger.error("Could not establish a connection to 'snapd.socker'")
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
def query(self, query: str) -> Optional[List[dict]]:
|
|
||||||
final_query = query.strip()
|
|
||||||
|
|
||||||
if final_query and self.session:
|
|
||||||
res = self.session.get(url=f'{URL_BASE}/find', params={'q': final_query})
|
|
||||||
|
|
||||||
if res.status_code == 200:
|
|
||||||
json_res = res.json()
|
|
||||||
|
|
||||||
if json_res['status-code'] == 200:
|
|
||||||
return json_res['result']
|
|
||||||
|
|
||||||
def find_by_name(self, name: str) -> Optional[List[dict]]:
|
|
||||||
if name and self.session:
|
|
||||||
res = self.session.get(f'{URL_BASE}/find?name={name}')
|
|
||||||
|
|
||||||
if res.status_code == 200:
|
|
||||||
json_res = res.json()
|
|
||||||
|
|
||||||
if json_res['status-code'] == 200:
|
|
||||||
return json_res['result']
|
|
||||||
|
|
||||||
def list_all_snaps(self) -> List[dict]:
|
|
||||||
if self.session:
|
|
||||||
res = self.session.get(f'{URL_BASE}/snaps')
|
|
||||||
|
|
||||||
if res.status_code == 200:
|
|
||||||
json_res = res.json()
|
|
||||||
|
|
||||||
if json_res['status-code'] == 200:
|
|
||||||
return json_res['result']
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
def list_only_apps(self) -> List[dict]:
|
|
||||||
if self.session:
|
|
||||||
res = self.session.get(f'{URL_BASE}/apps')
|
|
||||||
|
|
||||||
if res.status_code == 200:
|
|
||||||
json_res = res.json()
|
|
||||||
|
|
||||||
if json_res['status-code'] == 200:
|
|
||||||
return json_res['result']
|
|
||||||
return []
|
|
||||||
|
|
||||||
def list_commands(self, name: str) -> List[dict]:
|
|
||||||
if self.session:
|
|
||||||
res = self.session.get(f'{URL_BASE}/apps?names={name}')
|
|
||||||
|
|
||||||
if res.status_code == 200:
|
|
||||||
json_res = res.json()
|
|
||||||
|
|
||||||
if json_res['status-code'] == 200:
|
|
||||||
return [r for r in json_res['result'] if r['snap'] == name]
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def is_running() -> bool:
|
|
||||||
status = run_cmd('systemctl is-active snapd.service snapd.socket', print_error=False)
|
|
||||||
if not status:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
for status in status.split('\n'):
|
|
||||||
if status.strip().lower() == 'active':
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
Reference in New Issue
Block a user