Introduce async operations in webapp (#485)

* Introduce run_async for system processes

* Remove redundant code for system shutdown and reboot endpoints

* Use async process with systemd_service

* Modify innerHTML to show progress messages

* Add docstring
This commit is contained in:
Daniel Markstedt 2021-11-26 20:59:25 -08:00 committed by GitHub
parent a24a07508e
commit 5b397229cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 58 additions and 40 deletions

View File

@ -14,6 +14,7 @@ from ractl_cmds import (
list_devices,
reserve_scsi_ids,
)
from pi_cmds import run_async
from socket_cmds import send_pb_command
from settings import CFG_DIR, CONFIG_FILE_SUFFIX, PROPERTIES_SUFFIX, RESERVATIONS
import rascsi_interface_pb2 as proto
@ -175,42 +176,42 @@ def unzip_file(file_name, member=False, members=False):
members contains all of the full paths to each of the zip archive members
Returns (dict) with (boolean) status and (list of str) msg
"""
from subprocess import run
from re import escape
from asyncio import run
server_info = get_server_info()
prop_flag = False
if not member:
unzip_proc = run(
["unzip", "-d", server_info["image_dir"], "-n", "-j", \
f"{server_info['image_dir']}/{file_name}"], capture_output=True
)
unzip_proc = run(run_async(
f"unzip -d {server_info['image_dir']} -n -j "
f"{server_info['image_dir']}/{file_name}"
))
for path in members:
if path.endswith(PROPERTIES_SUFFIX):
name = PurePath(path).name
rename_file(f"{server_info['image_dir']}/{name}", f"{CFG_DIR}/{name}")
prop_flag = True
else:
unzip_proc = run(
["unzip", "-d", server_info["image_dir"], "-n", "-j", \
f"{server_info['image_dir']}/{file_name}", escape(member)], capture_output=True
)
from re import escape
member = escape(member)
unzip_proc = run(run_async(
f"unzip -d {server_info['image_dir']} -n -j "
f"{server_info['image_dir']}/{file_name} {member}"
))
# Attempt to unzip a properties file in the same archive dir
unzip_prop = run(
["unzip", "-d", CFG_DIR, "-n", "-j", \
f"{server_info['image_dir']}/{file_name}", escape(member) + "." + PROPERTIES_SUFFIX], capture_output=False
)
if unzip_prop.returncode == 0:
unzip_prop = run(run_async(
f"unzip -d {CFG_DIR} -n -j "
f"{server_info['image_dir']}/{file_name} {member}.{PROPERTIES_SUFFIX}"
))
if unzip_prop["returncode"] == 0:
prop_flag = True
if unzip_proc.returncode != 0:
stderr = unzip_proc.stderr.decode("utf-8")
logging.warning("Unzipping failed: %s", stderr)
return {"status": False, "msg": stderr}
if unzip_proc["returncode"] != 0:
logging.warning("Unzipping failed: %s", unzip_proc["stderr"])
return {"status": False, "msg": unzip_proc["stderr"]}
from re import findall
unzipped = findall(
"(?:inflating|extracting):(.+)\n",
unzip_proc.stdout.decode("utf-8")
unzip_proc["stdout"]
)
return {"status": True, "msg": unzipped, "prop_flag": prop_flag}
@ -404,7 +405,6 @@ def write_drive_properties(file_name, conf):
return {"status": False, "msg": f"Could not write to file: {file_path}"}
def read_drive_properties(path_name):
"""
Reads drive properties from json formatted file.

View File

@ -3,6 +3,8 @@ Module for methods controlling and getting information about the Pi's Linux syst
"""
import subprocess
import asyncio
import logging
from settings import AUTH_GROUP
@ -11,10 +13,8 @@ def systemd_service(service, action):
Takes (str) service and (str) action
Action can be one of start/stop/restart
"""
return (
subprocess.run(["sudo", "/bin/systemctl", action, service]).returncode
== 0
)
proc = asyncio.run(run_async("sudo /bin/systemctl {action} {service}"))
return proc["returncode"] == 0
def reboot_pi():
@ -119,6 +119,30 @@ def introspect_file(file_path, re_term):
return False
async def run_async(cmd):
"""
Takes (str) cmd with the shell command to execute
Executes shell command and captures output
Returns (dict) with (int) returncode, (str) stdout, (str) stderr
"""
proc = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
stdout, stderr = await proc.communicate()
logging.info("Executed command \"%s\" with status code %d", cmd, proc.returncode)
if stdout:
stdout = stdout.decode()
logging.info("stdout: %s", stdout)
if stderr:
stderr = stderr.decode()
logging.info("stderr: %s", stderr)
return {"returncode": proc.returncode, "stdout": stdout, "stderr": stderr}
def auth_active():
"""
Inspects if the group defined in AUTH_GROUP exists on the system.
@ -132,4 +156,4 @@ def auth_active():
"status": True,
"msg": "You must log in to use this function!",
}
return {"status": False, "msg": ""}
return {"status": False, "msg": ""}

View File

@ -59,7 +59,7 @@
</tbody>
</table>
</div>
<div class="flash">
<div class="flash" id="flash">
{% for category, message in get_flashed_messages(with_categories=true) %}
{% if category == "stdout" or category == "stderr" %}
<pre>{{ message }}</pre>

View File

@ -201,7 +201,7 @@
<form action="/files/unzip" method="post">
<input name="zip_file" type="hidden" value="{{ file['name'] }}">
<input name="zip_member" type="hidden" value="{{ member }}">
<input type="submit" value="Unzip">
<input type="submit" value="Unzip" onclick='document.getElementById("flash").innerHTML = "<div class=\"message\">Unzipping single file...</div>"'>
</form>
</summary>
<ul style="list-style: none;">
@ -215,7 +215,7 @@
<form action="/files/unzip" method="post">
<input name="zip_file" type="hidden" value="{{ file['name'] }}">
<input name="zip_member" type="hidden" value="{{ member }}">
<input type="submit" value="Unzip">
<input type="submit" value="Unzip" onclick='document.getElementById("flash").innerHTML = "<div class=\"message\">Unzipping single file...</div>"'>
</form>
{% endif %}
</li>
@ -243,7 +243,7 @@
<form action="/files/unzip" method="post">
<input name="zip_file" type="hidden" value="{{ file['name'] }}">
<input name="zip_members" type="hidden" value="{{ file['zip_members'] }}">
<input type="submit" value="Unzip All">
<input type="submit" value="Unzip All" onclick='document.getElementById("flash").innerHTML = "<div class=\"message\">Unzipping all files...</div>"'>
</form>
{% else %}
<form action="/scsi/attach" method="post">
@ -388,7 +388,7 @@
<form action="/files/download_to_images" method="post">
<label for="url">URL:</label>
<input name="url" placeholder="URL" required="" type="url">
<input type="submit" value="Download">
<input type="submit" value="Download" onclick='document.getElementById("flash").innerHTML = "<div class=\"message\">Downloading File to Images...</div>"'>
</form>
</td>
</tr>
@ -415,7 +415,7 @@
<form action="/files/download_to_afp" method="post">
<label for="url">URL:</label>
<input name="url" placeholder="URL" required="" type="url">
<input type="submit" value="Download">
<input type="submit" value="Download" onclick='document.getElementById("flash").innerHTML = "<div class=\"message\">Downloading File to AppleShare...</div>"'>
</form>
</td>
</tr>
@ -458,7 +458,7 @@
</select>
<label for="url">URL:</label>
<input name="url" placeholder="URL" required="" type="url">
<input type="submit" value="Download and Mount ISO">
<input type="submit" value="Download and Mount ISO" onclick='document.getElementById("flash").innerHTML = "<div class=\"message\">Downloading File as ISO...</div>"'>
</form>
</td>
</tr>

View File

@ -695,9 +695,6 @@ def restart():
flash(auth["msg"], "error")
return redirect(url_for("index"))
detach_all()
flash("Safely detached all devices.")
flash("Rebooting the Pi momentarily...")
reboot_pi()
return redirect(url_for("index"))
@ -730,9 +727,6 @@ def shutdown():
flash(auth["msg"], "error")
return redirect(url_for("index"))
detach_all()
flash("Safely detached all devices.")
flash("Shutting down the Pi momentarily...")
shutdown_pi()
return redirect(url_for("index"))
@ -940,7 +934,7 @@ def delete():
@APP.route("/files/unzip", methods=["POST"])
def unzip():
"""
Unzips a specified zip file
Unzips all files in a specified zip archive, or a single file in the zip archive
"""
auth = auth_active()
if auth["status"] and "username" not in session: