mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-07 02:24:16 +02:00
gem selector sketch
This commit is contained in:
2
bauh/gems/snap/__init__.py
Normal file
2
bauh/gems/snap/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
import os
|
||||
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
1
bauh/gems/snap/constants.py
Normal file
1
bauh/gems/snap/constants.py
Normal file
@@ -0,0 +1 @@
|
||||
SNAP_API_URL = 'https://search.apps.ubuntu.com/api/v1'
|
||||
145
bauh/gems/snap/controller.py
Normal file
145
bauh/gems/snap/controller.py
Normal file
@@ -0,0 +1,145 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Set, Type
|
||||
|
||||
from bauh.api.abstract.controller import SoftwareManager, SearchResult, ApplicationContext
|
||||
from bauh.api.abstract.disk import DiskCacheLoader
|
||||
from bauh.api.abstract.handler import ProcessWatcher
|
||||
from bauh.api.abstract.model import SoftwarePackage, PackageHistory, PackageUpdate, PackageSuggestion
|
||||
from bauh.commons.system import SystemProcess, ProcessHandler
|
||||
|
||||
from bauh.gems.snap import snap, suggestions
|
||||
from bauh.gems.snap.model import SnapApplication
|
||||
from bauh.gems.snap.worker import SnapAsyncDataLoader
|
||||
|
||||
|
||||
class SnapManager(SoftwareManager):
|
||||
|
||||
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)
|
||||
|
||||
def map_json(self, app_json: dict, installed: bool, disk_loader: DiskCacheLoader) -> SnapApplication:
|
||||
app = SnapApplication(publisher=app_json.get('publisher'),
|
||||
rev=app_json.get('rev'),
|
||||
notes=app_json.get('notes'),
|
||||
app_type=app_json.get('type'),
|
||||
id=app_json.get('name'),
|
||||
name=app_json.get('name'),
|
||||
version=app_json.get('version'),
|
||||
latest_version=app_json.get('version'),
|
||||
description=app_json.get('description'))
|
||||
|
||||
if app.publisher:
|
||||
app.publisher = app.publisher.replace('*', '')
|
||||
|
||||
app.installed = installed
|
||||
|
||||
api_data = self.api_cache.get(app_json['name'])
|
||||
expired_data = api_data and api_data.get('expires_at') and api_data['expires_at'] <= datetime.utcnow()
|
||||
|
||||
if (not api_data or expired_data) and app.is_application():
|
||||
if disk_loader and app.installed:
|
||||
disk_loader.fill(app)
|
||||
|
||||
SnapAsyncDataLoader(app=app, api_cache=self.api_cache, manager=self, context=self.context).start()
|
||||
else:
|
||||
app.fill_cached_data(api_data)
|
||||
|
||||
return app
|
||||
|
||||
def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1) -> SearchResult:
|
||||
installed = self.read_installed(disk_loader).installed
|
||||
|
||||
res = SearchResult([], [], 0)
|
||||
|
||||
for app_json in snap.search(words):
|
||||
|
||||
already_installed = None
|
||||
|
||||
if installed:
|
||||
already_installed = [i for i in installed if i.id == app_json.get('name')]
|
||||
already_installed = already_installed[0] if already_installed else None
|
||||
|
||||
if already_installed:
|
||||
res.installed.append(already_installed)
|
||||
else:
|
||||
res.new.append(self.map_json(app_json, installed=False, disk_loader=disk_loader))
|
||||
|
||||
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) -> SearchResult:
|
||||
installed = [self.map_json(app_json, installed=True, disk_loader=disk_loader) for app_json in snap.read_installed()]
|
||||
return SearchResult(installed, None, len(installed))
|
||||
|
||||
def downgrade(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
return ProcessHandler(watcher).handle(SystemProcess(subproc=snap.downgrade_and_stream(pkg.name, root_password), wrong_error_phrase=None))
|
||||
|
||||
def update(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> SystemProcess:
|
||||
raise Exception("'update' is not supported by {}".format(pkg.__class__.__name__))
|
||||
|
||||
def uninstall(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
return ProcessHandler(watcher).handle(SystemProcess(subproc=snap.uninstall_and_stream(pkg.name, root_password)))
|
||||
|
||||
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 = snap.get_info(pkg.name, attrs=('license', 'contact', 'commands', 'snap-id', 'tracking', 'installed'))
|
||||
info['description'] = pkg.description
|
||||
info['publisher'] = pkg.publisher
|
||||
info['revision'] = pkg.rev
|
||||
info['name'] = pkg.name
|
||||
|
||||
if info.get('commands'):
|
||||
info['commands'] = ' '.join(info['commands'])
|
||||
|
||||
return info
|
||||
|
||||
def get_history(self, pkg: SnapApplication) -> PackageHistory:
|
||||
raise Exception("'get_history' is not supported by {}".format(pkg.__class__.__name__))
|
||||
|
||||
def install(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
return ProcessHandler(watcher).handle(SystemProcess(subproc=snap.install_and_stream(pkg.name, pkg.confinement, root_password)))
|
||||
|
||||
def is_enabled(self) -> bool:
|
||||
return snap.is_installed()
|
||||
|
||||
def requires_root(self, action: str, pkg: SnapApplication):
|
||||
return action != 'search'
|
||||
|
||||
def refresh(self, pkg: SnapApplication, root_password: str, watcher: ProcessWatcher) -> bool:
|
||||
return ProcessHandler(watcher).handle(SystemProcess(subproc=snap.refresh_and_stream(pkg.name, root_password)))
|
||||
|
||||
def prepare(self):
|
||||
pass
|
||||
|
||||
def list_updates(self) -> List[PackageUpdate]:
|
||||
pass
|
||||
|
||||
def list_warnings(self) -> List[str]:
|
||||
if snap.get_snapd_version() == 'unavailable':
|
||||
return [self.i18n['snap.notification.snapd_unavailable']]
|
||||
|
||||
def list_suggestions(self, limit: int) -> List[PackageSuggestion]:
|
||||
res = []
|
||||
|
||||
sugs = [(i, p) for i, p in suggestions.ALL.items()]
|
||||
sugs.sort(key=lambda t: t[1].value, reverse=True)
|
||||
|
||||
for sug in sugs:
|
||||
|
||||
if limit <= 0 or len(res) < limit:
|
||||
found = snap.search(sug[0], exact_name=True)
|
||||
if found:
|
||||
res.append(PackageSuggestion(self.map_json(found[0], installed=False, disk_loader=None), sug[1]))
|
||||
else:
|
||||
break
|
||||
|
||||
return res
|
||||
88
bauh/gems/snap/model.py
Normal file
88
bauh/gems/snap/model.py
Normal file
@@ -0,0 +1,88 @@
|
||||
from typing import List
|
||||
|
||||
from bauh.api.abstract.model import SoftwarePackage, PackageAction
|
||||
from bauh.commons import resource
|
||||
|
||||
from bauh.gems.snap import ROOT_DIR
|
||||
|
||||
EXTRA_INSTALLED_ACTIONS = [
|
||||
PackageAction(i18n_status_key='snap.action.refresh.status',
|
||||
i18_label_key='snap.action.refresh.label',
|
||||
icon_path=resource.get_path('img/refresh.svg', ROOT_DIR),
|
||||
manager_method='refresh',
|
||||
requires_root=True)
|
||||
]
|
||||
|
||||
|
||||
class SnapApplication(SoftwarePackage):
|
||||
|
||||
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,
|
||||
app_type: str = None, confinement: str = None):
|
||||
super(SnapApplication, self).__init__(id=id, name=name, version=version,
|
||||
latest_version=latest_version, description=description)
|
||||
self.publisher = publisher
|
||||
self.rev = rev
|
||||
self.notes = notes
|
||||
self.type = app_type
|
||||
self.confinement = confinement
|
||||
|
||||
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.png', ROOT_DIR)
|
||||
|
||||
def get_type_icon_path(self):
|
||||
return self.get_default_icon_path()
|
||||
|
||||
def is_application(self):
|
||||
return not self.type and (self.type not in ('core', 'base', 'snapd') and not self._name_starts_with(('gtk-', 'gnome-', 'kde-', 'gtk2-')))
|
||||
|
||||
def _name_starts_with(self, words: tuple):
|
||||
for word in words:
|
||||
if self.name.startswith(word):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_disk_cache_path(self):
|
||||
return super(SnapApplication, self).get_disk_cache_path() + '/installed/' + self.name
|
||||
|
||||
def get_data_to_cache(self):
|
||||
return {
|
||||
"icon_url": self.icon_url,
|
||||
'confinement': self.confinement,
|
||||
'description': self.description
|
||||
}
|
||||
|
||||
def fill_cached_data(self, data: dict):
|
||||
if data:
|
||||
for base_attr in ('icon_url', 'description'):
|
||||
if data.get(base_attr):
|
||||
setattr(self, base_attr, data[base_attr])
|
||||
|
||||
if data.get('confinement'):
|
||||
self.confinement = data['confinement']
|
||||
|
||||
def get_command(self) -> str:
|
||||
return "snap run " + self.name
|
||||
|
||||
def can_be_run(self) -> bool:
|
||||
return self.installed and self.is_application()
|
||||
|
||||
def get_publisher(self):
|
||||
return self.publisher
|
||||
|
||||
def get_custom_supported_actions(self) -> List[PackageAction]:
|
||||
if self.installed:
|
||||
return EXTRA_INSTALLED_ACTIONS
|
||||
146
bauh/gems/snap/resources/img/refresh.svg
Executable file
146
bauh/gems/snap/resources/img/refresh.svg
Executable file
@@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<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:xlink="http://www.w3.org/1999/xlink"
|
||||
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="48"
|
||||
height="48"
|
||||
viewBox="0 0 47.999999 47.999999"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="refresh_2.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"><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></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs43"><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient540"><stop
|
||||
style="stop-color:#0088aa;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop536" /><stop
|
||||
style="stop-color:#0088aa;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop538" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient540"
|
||||
id="linearGradient542"
|
||||
x1="-130.74721"
|
||||
y1="-214.80638"
|
||||
x2="-556.49438"
|
||||
y2="110.01643"
|
||||
gradientUnits="userSpaceOnUse" /></defs><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="703"
|
||||
id="namedview41"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:zoom="1.9374833"
|
||||
inkscape:cx="165.58246"
|
||||
inkscape:cy="-10.288149"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
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" />
|
||||
<g
|
||||
id="g8"
|
||||
transform="matrix(0.10547014,0,0,0.0985161,-1.6940777,0)"
|
||||
style="fill:url(#linearGradient542);fill-opacity:1">
|
||||
<g
|
||||
id="g6"
|
||||
style="fill:url(#linearGradient542);fill-opacity:1">
|
||||
<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:url(#linearGradient542);fill-opacity:1" />
|
||||
<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:url(#linearGradient542);fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g10"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g12"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g14"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g16"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g18"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g20"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g22"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g24"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g26"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g28"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g30"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g32"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g34"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g36"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g38"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
BIN
bauh/gems/snap/resources/img/snap.png
Executable file
BIN
bauh/gems/snap/resources/img/snap.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
3
bauh/gems/snap/resources/locale/en
Normal file
3
bauh/gems/snap/resources/locale/en
Normal file
@@ -0,0 +1,3 @@
|
||||
snap.notification.snapd_unavailable=snapd seems not to be enabled. snap packages will not be available.
|
||||
snap.action.refresh.status=Refreshing
|
||||
snap.action.refresh.label=Refresh
|
||||
12
bauh/gems/snap/resources/locale/es
Normal file
12
bauh/gems/snap/resources/locale/es
Normal file
@@ -0,0 +1,12 @@
|
||||
snap.info.commands=comandos
|
||||
snap.info.contact=contacto
|
||||
snap.info.description=descripción
|
||||
snap.info.license=licencia
|
||||
snap.info.license.unset=no está definida
|
||||
snap.info.revision=revisión
|
||||
snap.info.tracking=tracking
|
||||
snap.info.installed=instalado
|
||||
snap.info.publisher=publicador
|
||||
snap.notification.snapd_unavailable=snapd no parece estar habilitado. los paquetes snap estarán indisponibles.
|
||||
snap.action.refresh.status=Actualizando
|
||||
snap.action.refresh.label=Actualizar
|
||||
12
bauh/gems/snap/resources/locale/pt
Normal file
12
bauh/gems/snap/resources/locale/pt
Normal file
@@ -0,0 +1,12 @@
|
||||
snap.info.commands=comandos
|
||||
snap.info.contact=contato
|
||||
snap.info.description=descrição
|
||||
snap.info.license=licença
|
||||
snap.info.license.unset=não definida
|
||||
snap.info.revision=revisão
|
||||
snap.info.tracking=tracking
|
||||
snap.info.installed=instalado
|
||||
snap.info.publisher=publicador
|
||||
snap.notification.snapd_unavailable=snapd não parece estar habilitado. os pacotes snap estarão indisponíveis.
|
||||
snap.action.refresh.status=Atualizando
|
||||
snap.action.refresh.label=Atualizar
|
||||
141
bauh/gems/snap/snap.py
Normal file
141
bauh/gems/snap/snap.py
Normal file
@@ -0,0 +1,141 @@
|
||||
import re
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
from bauh.commons.system import new_root_subprocess, run_cmd
|
||||
|
||||
BASE_CMD = 'snap'
|
||||
|
||||
|
||||
def is_installed():
|
||||
version = get_snapd_version()
|
||||
return False if version is None else True
|
||||
|
||||
|
||||
def get_version():
|
||||
res = run_cmd('{} --version'.format(BASE_CMD), print_error=False)
|
||||
return res.split('\n')[0].split(' ')[-1].strip() if res else None
|
||||
|
||||
|
||||
def get_snapd_version():
|
||||
res = run_cmd('{} --version'.format(BASE_CMD), print_error=False)
|
||||
|
||||
if not res:
|
||||
return None
|
||||
else:
|
||||
lines = res.split('\n')
|
||||
|
||||
if lines and len(lines) >= 2:
|
||||
version = lines[1].split(' ')[-1].strip()
|
||||
return version if version and version.lower() != 'unavailable' else None
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def app_str_to_json(app: str) -> dict:
|
||||
app_data = [word for word in app.split(' ') if word]
|
||||
app_json = {
|
||||
'name': app_data[0],
|
||||
'version': app_data[1],
|
||||
'rev': app_data[2],
|
||||
'tracking': app_data[3],
|
||||
'publisher': app_data[4] if len(app_data) >= 5 else None,
|
||||
'notes': app_data[5] if len(app_data) >= 6 else None
|
||||
}
|
||||
|
||||
app_json.update(get_info(app_json['name'], ('summary', 'type', 'description')))
|
||||
return app_json
|
||||
|
||||
|
||||
def get_info(app_name: str, attrs: tuple = None):
|
||||
full_info_lines = run_cmd('{} info {}'.format(BASE_CMD, app_name))
|
||||
|
||||
data = {}
|
||||
|
||||
if full_info_lines:
|
||||
re_attrs = r'\w+' if not attrs else '|'.join(attrs)
|
||||
info_map = re.findall(r'({}):\s+(.+)'.format(re_attrs), full_info_lines)
|
||||
|
||||
for info in info_map:
|
||||
data[info[0]] = info[1].strip()
|
||||
|
||||
if not attrs or 'description' in attrs:
|
||||
desc = re.findall(r'\|\n+((\s+.+\n+)+)', full_info_lines)
|
||||
data['description'] = ''.join([w.strip() for w in desc[0][0].strip().split('\n')]).replace('.', '.\n') if desc else None
|
||||
|
||||
if not attrs or 'commands' in attrs:
|
||||
commands = re.findall(r'commands:\s*\n*((\s+-\s.+\s*\n)+)', full_info_lines)
|
||||
data['commands'] = commands[0][0].replace('-', '').strip().split('\n') if commands else None
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def read_installed() -> List[dict]:
|
||||
res = run_cmd('{} list'.format(BASE_CMD), print_error=False)
|
||||
|
||||
apps = []
|
||||
|
||||
if res and len(res) > 0:
|
||||
lines = res.split('\n')
|
||||
|
||||
if not lines[0].startswith('error'):
|
||||
for idx, app_str in enumerate(lines):
|
||||
if idx > 0 and app_str:
|
||||
apps.append(app_str_to_json(app_str))
|
||||
|
||||
return apps
|
||||
|
||||
|
||||
def search(word: str, exact_name: bool = False) -> List[dict]:
|
||||
apps = []
|
||||
|
||||
res = run_cmd('{} find "{}"'.format(BASE_CMD, word), print_error=False)
|
||||
|
||||
if res:
|
||||
res = res.split('\n')
|
||||
|
||||
if not res[0].startswith('No matching'):
|
||||
for idx, app_str in enumerate(res):
|
||||
if idx > 0 and app_str:
|
||||
app_data = [word for word in app_str.split(' ') if word]
|
||||
|
||||
if exact_name and app_data[0] != word:
|
||||
continue
|
||||
|
||||
apps.append({
|
||||
'name': app_data[0],
|
||||
'version': app_data[1],
|
||||
'publisher': app_data[2],
|
||||
'notes': app_data[3] if app_data[3] != '-' else None,
|
||||
'summary': app_data[4] if len(app_data) == 5 else '',
|
||||
'rev': None,
|
||||
'tracking': None,
|
||||
'type': None
|
||||
})
|
||||
|
||||
if exact_name and len(apps) > 0:
|
||||
break
|
||||
|
||||
return apps
|
||||
|
||||
|
||||
def uninstall_and_stream(app_name: str, root_password: str):
|
||||
return new_root_subprocess([BASE_CMD, 'remove', app_name], root_password)
|
||||
|
||||
|
||||
def install_and_stream(app_name: str, confinement: str, root_password: str) -> subprocess.Popen:
|
||||
|
||||
install_cmd = [BASE_CMD, 'install', app_name] # default
|
||||
|
||||
if confinement == 'classic':
|
||||
install_cmd.append('--classic')
|
||||
|
||||
return new_root_subprocess(install_cmd, root_password)
|
||||
|
||||
|
||||
def downgrade_and_stream(app_name: str, root_password: str) -> subprocess.Popen:
|
||||
return new_root_subprocess([BASE_CMD, 'revert', app_name], root_password)
|
||||
|
||||
|
||||
def refresh_and_stream(app_name: str, root_password: str) -> subprocess.Popen:
|
||||
return new_root_subprocess([BASE_CMD, 'refresh', app_name], root_password)
|
||||
13
bauh/gems/snap/suggestions.py
Normal file
13
bauh/gems/snap/suggestions.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from bauh.api.abstract.model import SuggestionPriority
|
||||
|
||||
ALL = {
|
||||
'whatsdesk': SuggestionPriority.TOP,
|
||||
'slack': SuggestionPriority.HIGH,
|
||||
'discord': SuggestionPriority.HIGH,
|
||||
'yakyak': SuggestionPriority.MEDIUM,
|
||||
'instagraph': SuggestionPriority.MEDIUM,
|
||||
'gimp': SuggestionPriority.MEDIUM,
|
||||
'pycharm-professional': SuggestionPriority.LOW,
|
||||
'eclipse': SuggestionPriority.LOW,
|
||||
'supertuxkart': SuggestionPriority.LOW
|
||||
}
|
||||
71
bauh/gems/snap/worker.py
Normal file
71
bauh/gems/snap/worker.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import traceback
|
||||
from threading import Thread
|
||||
|
||||
from bauh.api.abstract.cache import MemoryCache
|
||||
from bauh.api.abstract.context import ApplicationContext
|
||||
from bauh.api.abstract.controller import SoftwareManager
|
||||
from bauh.api.abstract.model import PackageStatus
|
||||
|
||||
from bauh.gems.snap import snap
|
||||
from bauh.gems.snap.constants import SNAP_API_URL
|
||||
from bauh.gems.snap.model import SnapApplication
|
||||
|
||||
|
||||
class SnapAsyncDataLoader(Thread):
|
||||
|
||||
def __init__(self, app: SnapApplication, manager: SoftwareManager, api_cache: MemoryCache,
|
||||
context: ApplicationContext):
|
||||
super(SnapAsyncDataLoader, self).__init__(daemon=True)
|
||||
self.app = app
|
||||
self.id_ = '{}#{}'.format(self.__class__.__name__, id(self))
|
||||
self.manager = manager
|
||||
self.http_client = context.http_client
|
||||
self.api_cache = api_cache
|
||||
self.persist = False
|
||||
self.download_icons = context.download_icons
|
||||
self.logger = context.logger
|
||||
|
||||
def run(self):
|
||||
if self.app:
|
||||
self.app.status = PackageStatus.LOADING_DATA
|
||||
|
||||
try:
|
||||
res = self.http_client.session.get('{}/search?q={}'.format(SNAP_API_URL, self.app.name))
|
||||
|
||||
if res:
|
||||
try:
|
||||
snap_list = res.json()['_embedded']['clickindex:package']
|
||||
except:
|
||||
self.logger.warning('Snap API response responded differently from expected for app: {}'.format(self.app.name))
|
||||
return
|
||||
|
||||
if not snap_list:
|
||||
self.logger.warning("Could not retrieve app data for id '{}'. Server response: {}. Body: {}".format(self.app.id, res.status_code, res.content.decode()))
|
||||
else:
|
||||
snap_data = snap_list[0]
|
||||
|
||||
api_data = {
|
||||
'confinement': snap_data.get('confinement'),
|
||||
'description': snap_data.get('description'),
|
||||
'icon_url': snap_data.get('icon_url') if self.download_icons else None
|
||||
}
|
||||
|
||||
self.api_cache.add(self.app.id, api_data)
|
||||
self.app.confinement = api_data['confinement']
|
||||
self.app.icon_url = api_data['icon_url']
|
||||
|
||||
if not api_data.get('description'):
|
||||
api_data['description'] = snap.get_info(self.app.name, ('description',)).get('description')
|
||||
|
||||
self.app.description = api_data['description']
|
||||
self.persist = self.app.supports_disk_cache()
|
||||
else:
|
||||
self.logger.warning("Could not retrieve app data for id '{}'. Server response: {}. Body: {}".format(self.app.id, res.status_code, res.content.decode()))
|
||||
except:
|
||||
self.logger.error("Could not retrieve app data for id '{}'".format(self.app.id))
|
||||
traceback.print_exc()
|
||||
|
||||
self.app.status = PackageStatus.READY
|
||||
|
||||
if self.persist:
|
||||
self.manager.cache_to_disk(pkg=self.app, icon_bytes=None, only_icon=False)
|
||||
Reference in New Issue
Block a user