Simplify apk pipe handling, eliminate InstallItem
This commit is contained in:
parent
166e5b2ece
commit
bbdd91cd77
@ -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
|
||||
|
||||
|
@ -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 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:
|
||||
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)
|
||||
# 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
|
||||
try:
|
||||
packages = requests.get('{}/packages.json'.format(repo_conf['url']), auth=auth, timeout=5)
|
||||
if packages.status_code != 200:
|
||||
return packages.status_code
|
||||
except:
|
||||
return 0
|
||||
if packages.status_code == 200:
|
||||
self.online_packages = json.loads(packages.content)
|
||||
return 200
|
||||
return packages.status_code
|
||||
|
||||
def get_services_deps(self):
|
||||
# Fisrt, build a dictionary of {app: [needs]}
|
||||
|
@ -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):
|
||||
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 = '<span class="error">{}</span> <a href="#" class="app-clear-status">OK</a>'.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:
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user