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
|
# If the action finished without errors, restore nominal state by deleting the item from action list
|
||||||
self.clear_action(item.key)
|
self.clear_action(item.key)
|
||||||
except BaseException as e:
|
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:
|
with self.lock:
|
||||||
item.data = e
|
item.data = e
|
||||||
|
|
||||||
|
@ -1,20 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
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:
|
class AppMgr:
|
||||||
def __init__(self, conf):
|
def __init__(self, conf):
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
@ -64,26 +56,22 @@ class AppMgr:
|
|||||||
|
|
||||||
def install_app(self, item):
|
def install_app(self, item):
|
||||||
# Main installation function. Wrapper for installation via native package manager
|
# Main installation function. Wrapper for installation via native package manager
|
||||||
item.data = InstallItem()
|
item.data = 0
|
||||||
# Alpine apk provides machine-readable progress in bytes_downloaded/bytes_total format output to file descriptor of choice
|
# 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()
|
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:
|
with os.fdopen(pipe_rfd) as pipe_rf:
|
||||||
while p.poll() == None:
|
with subprocess.Popen(['apk', '--progress-fd', str(pipe_wfd), '--no-cache', 'add', 'vm-{}@vm'.format(item.key)], pass_fds=[pipe_wfd]) as p:
|
||||||
time.sleep(0.1)
|
# Close write pipe for vmmgr to not block the pipe once apk finishes
|
||||||
# Read pipe
|
os.close(pipe_wfd)
|
||||||
data = b''
|
while p.poll() == None:
|
||||||
while True:
|
# Wait for line end or EOF in read pipe and process it
|
||||||
chunk = os.read(pipe_rfd, 8192)
|
data = pipe_rf.readline()
|
||||||
data += chunk
|
if data:
|
||||||
if len(chunk) < 8192:
|
progress = data.rstrip().split('/')
|
||||||
break
|
item.data = math.floor(int(progress[0]) / int(progress[1]) * 100)
|
||||||
# Parse last apk progress line
|
# If the apk command didn't finish with returncode 0, raise an exception
|
||||||
progress = data.decode().splitlines()[-1].split('/')
|
if p.returncode:
|
||||||
item.data.bytes_downloaded = progress[0]
|
raise CalledProcessError(p.returncode, cmd)
|
||||||
item.data.bytes_total = progress[1]
|
|
||||||
# Close pipe
|
|
||||||
os.close(pipe_rfd)
|
|
||||||
os.close(pipe_wfd)
|
|
||||||
|
|
||||||
def uninstall_app(self, item):
|
def uninstall_app(self, item):
|
||||||
# Main uninstallation function. Wrapper for uninstallation via native package manager
|
# Main uninstallation function. Wrapper for uninstallation via native package manager
|
||||||
@ -93,16 +81,18 @@ class AppMgr:
|
|||||||
self.update_app_autostart(app, False)
|
self.update_app_autostart(app, False)
|
||||||
if name in self.conf['apps']:
|
if name in self.conf['apps']:
|
||||||
del self.conf['apps'][name]
|
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):
|
def fetch_online_packages(self, repo_conf):
|
||||||
# Fetches list of online packages
|
# Fetches list of online packages
|
||||||
auth = (repo_conf['user'], repo_conf['pwd']) if repo_conf['user'] else None
|
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)
|
try:
|
||||||
if packages.status_code != 200:
|
packages = requests.get('{}/packages.json'.format(repo_conf['url']), auth=auth, timeout=5)
|
||||||
return packages.status_code
|
except:
|
||||||
self.online_packages = json.loads(packages.content)
|
return 0
|
||||||
return 200
|
if packages.status_code == 200:
|
||||||
|
self.online_packages = json.loads(packages.content)
|
||||||
|
return packages.status_code
|
||||||
|
|
||||||
def get_services_deps(self):
|
def get_services_deps(self):
|
||||||
# Fisrt, build a dictionary of {app: [needs]}
|
# Fisrt, build a dictionary of {app: [needs]}
|
||||||
|
@ -164,11 +164,8 @@ class WSGIApp:
|
|||||||
# Application manager view.
|
# Application manager view.
|
||||||
repo_error = None
|
repo_error = None
|
||||||
repo_conf = self.vmmgr.get_repo_conf()
|
repo_conf = self.vmmgr.get_repo_conf()
|
||||||
try:
|
status = self.appmgr.fetch_online_packages(repo_conf)
|
||||||
status = self.appmgr.fetch_online_packages(repo_conf)
|
if status == 401:
|
||||||
except InvalidSignature:
|
|
||||||
repo_error = request.session.lang.invalid_packages_signature()
|
|
||||||
if status in (401, 403):
|
|
||||||
repo_error = request.session.lang.repo_invalid_credentials()
|
repo_error = request.session.lang.repo_invalid_credentials()
|
||||||
elif status != 200:
|
elif status != 200:
|
||||||
repo_error = request.session.lang.repo_unavailable()
|
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())
|
status = '<span class="error">{}</span> <a href="#" class="app-clear-status">OK</a>'.format(lang.package_manager_error())
|
||||||
actions = None
|
actions = None
|
||||||
else:
|
else:
|
||||||
if item.data.stage == 0:
|
if item.data < 100:
|
||||||
status = '{} ({} %)'.format(lang.status_downloading(), item.data)
|
status = '{} ({} %)'.format(lang.status_downloading(), item.data)
|
||||||
elif item.data.stage == 1:
|
|
||||||
status = lang.status_installing_deps()
|
|
||||||
else:
|
else:
|
||||||
status = lang.status_installing()
|
status = lang.status_installing()
|
||||||
elif item.action == self.appmgr.uninstall_app:
|
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.',
|
'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í.',
|
'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.',
|
'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_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',
|
'repo_unavailable': 'Distribuční server není dostupný. Zkontroluje připojení k síti',
|
||||||
'bad_password': 'Nesprávné heslo',
|
'bad_password': 'Nesprávné heslo',
|
||||||
@ -38,7 +37,6 @@ class WSGILang:
|
|||||||
'status_stopping': 'Zastavuje se',
|
'status_stopping': 'Zastavuje se',
|
||||||
'status_stopped': 'Zastavena',
|
'status_stopped': 'Zastavena',
|
||||||
'status_downloading': 'Stahuje se',
|
'status_downloading': 'Stahuje se',
|
||||||
'status_installing_deps': 'Instalují se závislosti',
|
|
||||||
'status_installing': 'Instaluje se',
|
'status_installing': 'Instaluje se',
|
||||||
'status_uninstalling': 'Odinstalovává se',
|
'status_uninstalling': 'Odinstalovává se',
|
||||||
'status_not_installed': 'Není nainstalována',
|
'status_not_installed': 'Není nainstalována',
|
||||||
|
Loading…
Reference in New Issue
Block a user