This commit is contained in:
Vinícius Moreira
2019-07-25 17:48:53 -03:00
committed by GitHub
10 changed files with 55 additions and 70 deletions

View File

@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [0.4.1] - 2019-07-25
### Improvements
- retrieving some Snaps data (icon, description and confinement) through Ubuntu API instead of Snap Store
### Fixes:
- not showing app disk cached description when listing installed apps
- not updating disk cached data of installed apps
## [0.4.0] - 2019-07-25 ## [0.4.0] - 2019-07-25
### Features ### Features
- supporting snaps - supporting snaps

View File

@@ -1,4 +1,4 @@
__version__ = '0.4.0' __version__ = '0.4.1'
__app_name__ = 'fpakman' __app_name__ = 'fpakman'
import os import os

View File

@@ -21,7 +21,7 @@ class FlatpakAsyncDataLoader(AsyncDataLoader):
self.http_session = http_session self.http_session = http_session
self.attempts = attempts self.attempts = attempts
self.api_cache = api_cache self.api_cache = api_cache
self.to_persist = {} # stores all data loaded by the instance self.persist = False
self.timeout = timeout self.timeout = timeout
def run(self): def run(self):
@@ -55,10 +55,7 @@ class FlatpakAsyncDataLoader(AsyncDataLoader):
self.api_cache.add(self.app.base_data.id, loaded_data) self.api_cache.add(self.app.base_data.id, loaded_data)
self.app.status = ApplicationStatus.READY self.app.status = ApplicationStatus.READY
self.persist = self.app.supports_disk_cache()
if self.app.supports_disk_cache():
self.to_persist[self.app.base_data.id] = self.app
break break
else: else:
self.log_msg("Could not retrieve app data for id '{}'. Server response: {}. Body: {}".format( self.log_msg("Could not retrieve app data for id '{}'. Server response: {}. Body: {}".format(
@@ -70,12 +67,8 @@ class FlatpakAsyncDataLoader(AsyncDataLoader):
self.app.status = ApplicationStatus.READY self.app.status = ApplicationStatus.READY
def cache_to_disk(self): if self.persist:
if self.to_persist: self.manager.cache_to_disk(app=self.app, icon_bytes=None, only_icon=False)
for app in self.to_persist.values():
self.manager.cache_to_disk(app=app, icon_bytes=None, only_icon=False)
self.to_persist = {}
def clone(self) -> "FlatpakAsyncDataLoader": def clone(self) -> "FlatpakAsyncDataLoader":
return FlatpakAsyncDataLoader(manager=self.manager, return FlatpakAsyncDataLoader(manager=self.manager,

View File

@@ -1,4 +1,4 @@
from fpakman.core.constants import CACHE_PATH from fpakman.core.constants import CACHE_PATH
SNAP_STORE_URL = 'https://snapcraft.io' SNAP_API_URL = 'https://search.apps.ubuntu.com/api/v1'
SNAP_CACHE_PATH = '{}/snap/installed'.format(CACHE_PATH) SNAP_CACHE_PATH = '{}/snap/installed'.format(CACHE_PATH)

View File

@@ -74,8 +74,7 @@ class SnapManager(ApplicationManager):
return res return res
def read_installed(self, disk_loader: DiskCacheLoader) -> List[SnapApplication]: def read_installed(self, disk_loader: DiskCacheLoader) -> List[SnapApplication]:
res = [self.map_json(app_json, installed=True, disk_loader=disk_loader) for app_json in snap.read_installed()] return [self.map_json(app_json, installed=True, disk_loader=disk_loader) for app_json in snap.read_installed()]
return res
def downgrade_app(self, app: Application, root_password: str) -> FpakmanProcess: def downgrade_app(self, app: Application, root_password: str) -> FpakmanProcess:
return FpakmanProcess(subproc=snap.downgrade_and_stream(app.base_data.name, root_password), wrong_error_phrase=None) return FpakmanProcess(subproc=snap.downgrade_and_stream(app.base_data.name, root_password), wrong_error_phrase=None)
@@ -114,7 +113,7 @@ class SnapManager(ApplicationManager):
return [] return []
def install_and_stream(self, app: SnapApplication, root_password: str) -> FpakmanProcess: def install_and_stream(self, app: SnapApplication, root_password: str) -> FpakmanProcess:
return FpakmanProcess(subproc=snap.install_and_stream(app.base_data.name, app.install_cmd, root_password)) return FpakmanProcess(subproc=snap.install_and_stream(app.base_data.name, app.confinement, root_password))
def is_enabled(self) -> bool: def is_enabled(self) -> bool:
return snap.is_installed() return snap.is_installed()

View File

@@ -5,13 +5,13 @@ from fpakman.core.snap.constants import SNAP_CACHE_PATH
class SnapApplication(Application): class SnapApplication(Application):
def __init__(self, base_data: ApplicationData, publisher: str, rev: str, notes: str, app_type: str, install_cmd: str = None): def __init__(self, base_data: ApplicationData, publisher: str, rev: str, notes: str, app_type: str, confinement: str = None):
super(SnapApplication, self).__init__(base_data=base_data) super(SnapApplication, self).__init__(base_data=base_data)
self.publisher = publisher self.publisher = publisher
self.rev = rev self.rev = rev
self.notes = notes self.notes = notes
self.type = app_type self.type = app_type
self.install_cmd = install_cmd self.confinement = confinement
def has_history(self): def has_history(self):
return False return False
@@ -46,7 +46,7 @@ class SnapApplication(Application):
def get_data_to_cache(self): def get_data_to_cache(self):
return { return {
"icon_url": self.base_data.icon_url, "icon_url": self.base_data.icon_url,
'install_cmd': self.install_cmd, 'confinement': self.confinement,
'description': self.base_data.description 'description': self.base_data.description
} }
@@ -56,5 +56,5 @@ class SnapApplication(Application):
if data.get(base_attr): if data.get(base_attr):
setattr(self.base_data, base_attr, data[base_attr]) setattr(self.base_data, base_attr, data[base_attr])
if data.get('install_cmd'): if data.get('confinement'):
self.install_cmd = data['install_cmd'] self.confinement = data['confinement']

View File

@@ -2,11 +2,7 @@ import re
import subprocess import subprocess
from typing import List from typing import List
import requests
from bs4 import BeautifulSoup
from fpakman.core import system from fpakman.core import system
from fpakman.core.snap.constants import SNAP_STORE_URL
BASE_CMD = 'snap' BASE_CMD = 'snap'
@@ -120,19 +116,12 @@ def uninstall_and_stream(app_name: str, root_password: str):
return system.cmd_as_root([BASE_CMD, 'remove', app_name], root_password) return system.cmd_as_root([BASE_CMD, 'remove', app_name], root_password)
def install_and_stream(app_name: str, custom_install_cmd: str, root_password: str) -> subprocess.Popen: def install_and_stream(app_name: str, confinement: str, root_password: str) -> subprocess.Popen:
install_cmd = [BASE_CMD, 'install', app_name] # default install_cmd = [BASE_CMD, 'install', app_name] # default
if custom_install_cmd: if confinement == 'classic':
install_cmd = custom_install_cmd.split(' ') install_cmd.append('--classic')
else: # tries to retrieve the snapstore proper installation command
res = requests.get('{}/{}'.format(SNAP_STORE_URL, app_name))
if res.status_code == 200:
soup = BeautifulSoup(res.text, 'html.parser')
input_install_cmd = soup.find("input", {"id": "snap-install"})
install_cmd = input_install_cmd.get("value").strip().split(' ')
return system.cmd_as_root(install_cmd, root_password) return system.cmd_as_root(install_cmd, root_password)

View File

@@ -1,13 +1,12 @@
import time import time
import traceback import traceback
from bs4 import BeautifulSoup
from colorama import Fore from colorama import Fore
from fpakman.core.controller import ApplicationManager from fpakman.core.controller import ApplicationManager
from fpakman.core.model import ApplicationStatus from fpakman.core.model import ApplicationStatus
from fpakman.core.snap import snap from fpakman.core.snap import snap
from fpakman.core.snap.constants import SNAP_STORE_URL from fpakman.core.snap.constants import SNAP_API_URL
from fpakman.core.snap.model import SnapApplication from fpakman.core.snap.model import SnapApplication
from fpakman.core.worker import AsyncDataLoader from fpakman.core.worker import AsyncDataLoader
from fpakman.util.cache import Cache from fpakman.util.cache import Cache
@@ -22,55 +21,60 @@ class SnapAsyncDataLoader(AsyncDataLoader):
self.attempts = attempts self.attempts = attempts
self.api_cache = api_cache self.api_cache = api_cache
self.timeout = timeout self.timeout = timeout
self.to_persist = {} # stores all data loaded by the instance self.persist = False
self.download_icons = download_icons self.download_icons = download_icons
def run(self): def run(self):
if self.app: if self.app:
self.app.status = ApplicationStatus.LOADING_DATA self.app.status = ApplicationStatus.LOADING_DATA
if not self.app.base_data.description:
self.app.base_data.description = snap.get_info(self.app.base_data.name, ('description',)).get('description')
for _ in range(0, self.attempts): for _ in range(0, self.attempts):
try: try:
res = self.http_session.get('{}/{}'.format(SNAP_STORE_URL, self.app.base_data.name), res = self.http_session.get('{}/search?q={}'.format(SNAP_API_URL, self.app.base_data.name), timeout=self.timeout)
timeout=self.timeout)
if res.status_code == 200 and res.text: if res.status_code == 200 and res.text:
soup = BeautifulSoup(res.text, 'html.parser')
input_install_cmd = soup.find("input", {"id": "snap-install"}) try:
snap_list = res.json()['_embedded']['clickindex:package']
except:
self.log_msg('Snap API response responded differently from expected for app: {}'.format(self.app.base_data.name))
break
if not snap_list:
break
snap_data = snap_list[0]
api_data = { api_data = {
'install_cmd': input_install_cmd.get("value").strip(), 'confinement': snap_data.get('confinement'),
'description': self.app.base_data.description 'description': snap_data.get('description'),
'icon_url': snap_data.get('icon_url') if self.download_icons else None
} }
img_icon = None
if self.download_icons:
img_icon = soup.find('img', {"class": "p-snap-heading__icon"})
if img_icon and img_icon.get("src") and 'snapcraft-missing-icon' not in img_icon.get('src'):
api_data['icon_url'] = img_icon.get("src")
self.api_cache.add(self.app.base_data.id, api_data) self.api_cache.add(self.app.base_data.id, api_data)
self.app.install_cmd = api_data['install_cmd'] self.app.confinement = api_data['confinement']
self.app.base_data.icon_url = api_data.get('icon_url') self.app.base_data.icon_url = api_data['icon_url']
if not api_data.get('description'):
api_data['description'] = snap.get_info(self.app.base_data.name, ('description',)).get('description')
self.app.base_data.description = api_data['description']
self.app.status = ApplicationStatus.READY self.app.status = ApplicationStatus.READY
self.persist = self.app.supports_disk_cache()
if self.app.supports_disk_cache():
self.to_persist[self.app.base_data.id] = self.app
break break
else: else:
self.log_msg("Could not retrieve app data for id '{}'. Server response: {}. Body: {}".format(self.app.base_data.id, res.status_code, res.content.decode()), Fore.RED) self.log_msg("Could not retrieve app data for id '{}'. Server response: {}. Body: {}".format(self.app.base_data.id, res.status_code, res.content.decode()), Fore.RED)
except: except:
self.log_msg("Could not retrieve app data for id '{}'".format(app.base_data.id), Fore.YELLOW) self.log_msg("Could not retrieve app data for id '{}'".format(self.app.base_data.id), Fore.YELLOW)
traceback.print_exc() traceback.print_exc()
time.sleep(0.5) time.sleep(0.5)
self.app.status = ApplicationStatus.READY self.app.status = ApplicationStatus.READY
if self.persist:
self.manager.cache_to_disk(app=self.app, icon_bytes=None, only_icon=False)
def clone(self) -> "SnapAsyncDataLoader": def clone(self) -> "SnapAsyncDataLoader":
return SnapAsyncDataLoader(manager=self.manager, return SnapAsyncDataLoader(manager=self.manager,
api_cache=self.api_cache, api_cache=self.api_cache,
@@ -79,9 +83,3 @@ class SnapAsyncDataLoader(AsyncDataLoader):
timeout=self.timeout, timeout=self.timeout,
app=self.app) app=self.app)
def cache_to_disk(self):
if self.to_persist:
for app in self.to_persist.values():
self.manager.cache_to_disk(app=app, icon_bytes=None, only_icon=False)
self.to_persist = {}

View File

@@ -19,7 +19,7 @@ class ApplicationView:
def get_async_attr(self, attr: str, strip_html: bool = False, default: str = '...'): def get_async_attr(self, attr: str, strip_html: bool = False, default: str = '...'):
if self.model.is_library(): if getattr(self.model.base_data, attr) is not None or self.model.is_library():
res = getattr(self.model.base_data, attr) res = getattr(self.model.base_data, attr)
else: else:
res = getattr(self.model.base_data, attr) if self.model.status == ApplicationStatus.READY and getattr(self.model.base_data, attr) else default res = getattr(self.model.base_data, attr) if self.model.status == ApplicationStatus.READY and getattr(self.model.base_data, attr) else default

View File

@@ -1,4 +1,3 @@
pyqt5>=5.12 pyqt5>=5.12
requests>=2.22 requests>=2.22
colorama>=0.4.1 colorama>=0.4.1
beautifulsoup4>=4.7