From bbdd91cd77c3c11ac691e7957ad0480f3fba74b0 Mon Sep 17 00:00:00 2001 From: Disassembler Date: Fri, 15 Feb 2019 22:24:50 +0100 Subject: [PATCH] Simplify apk pipe handling, eliminate InstallItem --- usr/lib/python3.6/vmmgr/actionqueue.py | 2 +- usr/lib/python3.6/vmmgr/appmgr.py | 58 +++++++++++--------------- usr/lib/python3.6/vmmgr/wsgiapp.py | 11 ++--- usr/lib/python3.6/vmmgr/wsgilang.py | 2 - 4 files changed, 28 insertions(+), 45 deletions(-) diff --git a/usr/lib/python3.6/vmmgr/actionqueue.py b/usr/lib/python3.6/vmmgr/actionqueue.py index de3376f..c8366ee 100644 --- a/usr/lib/python3.6/vmmgr/actionqueue.py +++ b/usr/lib/python3.6/vmmgr/actionqueue.py @@ -57,7 +57,7 @@ class ActionQueue: # If the action finished without errors, restore nominal state by deleting the item from action list self.clear_action(item.key) except BaseException as e: - # If the action failed, store the exception and leave it in the list form manual clearance + # If the action failed, store the exception and leave it in the list for manual clearance with self.lock: item.data = e diff --git a/usr/lib/python3.6/vmmgr/appmgr.py b/usr/lib/python3.6/vmmgr/appmgr.py index 1c79d2c..147fcee 100644 --- a/usr/lib/python3.6/vmmgr/appmgr.py +++ b/usr/lib/python3.6/vmmgr/appmgr.py @@ -1,20 +1,12 @@ # -*- coding: utf-8 -*- import json +import math import os import requests import subprocess import time -class InstallItem: - def __init__(self): - self.bytes_total = 1 - self.bytes_downloaded = 0 - - @property - def percent_downloaded(self): - return round(self.bytes_downloaded / self.bytes_total * 100) - class AppMgr: def __init__(self, conf): self.conf = conf @@ -64,26 +56,22 @@ class AppMgr: def install_app(self, item): # Main installation function. Wrapper for installation via native package manager - item.data = InstallItem() - # Alpine apk provides machine-readable progress in bytes_downloaded/bytes_total format output to file descriptor of choice + item.data = 0 + # Alpine apk provides machine-readable progress in bytes_downloaded/bytes_total format output to file descriptor of choice, so create a pipe for it pipe_rfd, pipe_wfd = os.pipe() - with subprocess.Popen(['apk', '--progress-fd', str(pipe_wfd), '--no-cache', 'add', 'vm-{}@vm'.format(item.key)], pass_fds=[pipe_wfd]) as p: - while p.poll() == None: - time.sleep(0.1) - # Read pipe - data = b'' - while True: - chunk = os.read(pipe_rfd, 8192) - data += chunk - if len(chunk) < 8192: - break - # Parse last apk progress line - progress = data.decode().splitlines()[-1].split('/') - item.data.bytes_downloaded = progress[0] - item.data.bytes_total = progress[1] - # Close pipe - os.close(pipe_rfd) - os.close(pipe_wfd) + with os.fdopen(pipe_rfd) as pipe_rf: + with subprocess.Popen(['apk', '--progress-fd', str(pipe_wfd), '--no-cache', 'add', 'vm-{}@vm'.format(item.key)], pass_fds=[pipe_wfd]) as p: + # Close write pipe for vmmgr to not block the pipe once apk finishes + os.close(pipe_wfd) + while p.poll() == None: + # Wait for line end or EOF in read pipe and process it + data = pipe_rf.readline() + if data: + progress = data.rstrip().split('/') + item.data = math.floor(int(progress[0]) / int(progress[1]) * 100) + # If the apk command didn't finish with returncode 0, raise an exception + if p.returncode: + raise CalledProcessError(p.returncode, cmd) def uninstall_app(self, item): # Main uninstallation function. Wrapper for uninstallation via native package manager @@ -93,16 +81,18 @@ class AppMgr: self.update_app_autostart(app, False) if name in self.conf['apps']: del self.conf['apps'][name] - subprocess.run(['apk', '--no-cache', 'del', 'vm-{}@vm'.format(app)]) + subprocess.run(['apk', '--no-cache', 'del', 'vm-{}@vm'.format(app)], check=True) def fetch_online_packages(self, repo_conf): # Fetches list of online packages auth = (repo_conf['user'], repo_conf['pwd']) if repo_conf['user'] else None - packages = requests.get('{}/packages.json'.format(repo_conf['url']), auth=auth, timeout=5) - if packages.status_code != 200: - return packages.status_code - self.online_packages = json.loads(packages.content) - return 200 + try: + packages = requests.get('{}/packages.json'.format(repo_conf['url']), auth=auth, timeout=5) + except: + return 0 + if packages.status_code == 200: + self.online_packages = json.loads(packages.content) + return packages.status_code def get_services_deps(self): # Fisrt, build a dictionary of {app: [needs]} diff --git a/usr/lib/python3.6/vmmgr/wsgiapp.py b/usr/lib/python3.6/vmmgr/wsgiapp.py index 2c44dcf..acd53b6 100644 --- a/usr/lib/python3.6/vmmgr/wsgiapp.py +++ b/usr/lib/python3.6/vmmgr/wsgiapp.py @@ -164,11 +164,8 @@ class WSGIApp: # Application manager view. repo_error = None repo_conf = self.vmmgr.get_repo_conf() - try: - status = self.appmgr.fetch_online_packages(repo_conf) - except InvalidSignature: - repo_error = request.session.lang.invalid_packages_signature() - if status in (401, 403): + status = self.appmgr.fetch_online_packages(repo_conf) + if status == 401: repo_error = request.session.lang.repo_invalid_credentials() elif status != 200: repo_error = request.session.lang.repo_unavailable() @@ -212,10 +209,8 @@ class WSGIApp: status = '{} OK'.format(lang.package_manager_error()) actions = None else: - if item.data.stage == 0: + if item.data < 100: status = '{} ({} %)'.format(lang.status_downloading(), item.data) - elif item.data.stage == 1: - status = lang.status_installing_deps() else: status = lang.status_installing() elif item.action == self.appmgr.uninstall_app: diff --git a/usr/lib/python3.6/vmmgr/wsgilang.py b/usr/lib/python3.6/vmmgr/wsgilang.py index b023bd6..37bb726 100644 --- a/usr/lib/python3.6/vmmgr/wsgilang.py +++ b/usr/lib/python3.6/vmmgr/wsgilang.py @@ -23,7 +23,6 @@ class WSGILang: 'stop_start_error': 'Došlo k chybě při spouštění/zastavování. Zkuste akci opakovat nebo restartuje virtuální stroj.', 'installation_in_progress': 'Probíhá instalace jiného balíku. Vyčkejte na její dokončení.', 'package_manager_error': 'Došlo k chybě při instalaci aplikace. Zkuste akci opakovat nebo restartuje virtuální stroj.', - 'invalid_packages_signature': 'Digitální podpis seznamu balíků není platný. Kontaktujte správce distribučního serveru.', 'repo_invalid_credentials': 'Přístupové údaje k distribučnímu serveru nejsou správné.', 'repo_unavailable': 'Distribuční server není dostupný. Zkontroluje připojení k síti', 'bad_password': 'Nesprávné heslo', @@ -38,7 +37,6 @@ class WSGILang: 'status_stopping': 'Zastavuje se', 'status_stopped': 'Zastavena', 'status_downloading': 'Stahuje se', - 'status_installing_deps': 'Instalují se závislosti', 'status_installing': 'Instaluje se', 'status_uninstalling': 'Odinstalovává se', 'status_not_installed': 'Není nainstalována',