Web UI: Upload to tmp file name then rename if successful (#1272)

* Upload to tmp file name then rename if successful

* Move the dropzone.js operations back into web.py

* Move list_images() from file commands into piscsi commands (it was the only class method in that package that calls the protobuf interface)

* Remove now-redundant helptext
This commit is contained in:
Daniel Markstedt
2023-10-31 14:54:04 -07:00
committed by GitHub
parent 7bbcf59c76
commit 029cf06c72
6 changed files with 141 additions and 145 deletions
-1
View File
@@ -4,7 +4,6 @@
<h2>{{ _("Upload File from Local Computer") }}</h2>
<ul>
<li>{{ _("The largest file size accepted in this form is %(max_file_size)s MiB. Use other file transfer means for larger files.", max_file_size=max_file_size) }}</li>
<li>{{ _("You have to manually clean up partially uploaded files, as a result of cancelling the upload or closing this page.") }}</li>
<li>{{ _("Disk Images") }} = {{ env["image_dir"] }}</li>
{% if file_server_dir_exists %}
<li>{{ _("Shared Files") }} = {{ FILE_SERVER_DIR }}</li>
+41 -7
View File
@@ -8,11 +8,13 @@ import argparse
from pathlib import Path, PurePath
from functools import wraps
from grp import getgrall
from os import path
import bjoern
from piscsi.return_codes import ReturnCodes
from simplepam import authenticate
from flask_babel import Babel, Locale, refresh, _
from werkzeug.utils import secure_filename
from flask import (
Flask,
@@ -55,7 +57,6 @@ from web_utils import (
auth_active,
is_bridge_configured,
is_safe_path,
upload_with_dropzonejs,
browser_supports_modern_themes,
)
from settings import (
@@ -225,9 +226,8 @@ def index():
devices = piscsi_cmd.list_devices()
device_types = map_device_types_and_names(piscsi_cmd.get_device_types()["device_types"])
image_files = file_cmd.list_images()
image_files = piscsi_cmd.list_images()
config_files = file_cmd.list_config_files()
ip_addr, host = sys_cmd.get_ip_and_host()
formatted_image_files = format_image_list(
image_files["files"], Path(server_info["image_dir"]).name, device_types
)
@@ -315,7 +315,7 @@ def drive_list():
return response(
template="drives.html",
page_title=_("PiSCSI Create Drive"),
files=file_cmd.list_images()["files"],
files=piscsi_cmd.list_images()["files"],
drive_properties=format_drive_properties(APP.config["PISCSI_DRIVE_PROPERTIES"]),
)
@@ -1035,7 +1035,41 @@ def upload_file():
else:
return make_response(_("Unknown destination"), 403)
return upload_with_dropzonejs(destination_dir)
log = logging.getLogger("pydrop")
file_object = request.files["file"]
file_name = secure_filename(file_object.filename)
tmp_file_name = "__tmp_" + file_name
save_path = path.join(destination_dir, file_name)
tmp_save_path = path.join(destination_dir, tmp_file_name)
current_chunk = int(request.form["dzchunkindex"])
# Makes sure not to overwrite an existing file,
# but continues writing to a file transfer in progress
if path.exists(save_path) and current_chunk == 0:
return make_response(_("The file already exists!"), 400)
try:
with open(tmp_save_path, "ab") as save:
save.seek(int(request.form["dzchunkbyteoffset"]))
save.write(file_object.stream.read())
except OSError:
log.exception("Could not write to file")
return make_response(_("Unable to write the file to disk!"), 500)
total_chunks = int(request.form["dztotalchunkcount"])
if current_chunk + 1 == total_chunks:
# Validate the resulting file size after writing the last chunk
if path.getsize(tmp_save_path) != int(request.form["dztotalfilesize"]):
log.error("File size mismatch between the original file and transferred file.")
return make_response(_("Transferred file corrupted!"), 500)
process = file_cmd.rename_file(Path(tmp_save_path), Path(save_path))
if not process["status"]:
return make_response(_("Unable to rename temporary file!"), 500)
return make_response(_("File upload successful!"), 200)
@APP.route("/files/create", methods=["POST"])
@@ -1487,7 +1521,7 @@ if __name__ == "__main__":
sock_cmd = SocketCmdsFlask(host=arguments.backend_host, port=arguments.backend_port)
piscsi_cmd = PiscsiCmds(sock_cmd=sock_cmd, token=APP.config["PISCSI_TOKEN"])
file_cmd = FileCmds(sock_cmd=sock_cmd, piscsi=piscsi_cmd, token=APP.config["PISCSI_TOKEN"])
file_cmd = FileCmds(piscsi=piscsi_cmd)
sys_cmd = SysCmds()
if not piscsi_cmd.is_token_auth()["status"] and not APP.config["PISCSI_TOKEN"]:
+1 -38
View File
@@ -4,12 +4,11 @@ Module for PiSCSI Web Interface utility methods
import logging
from grp import getgrall
from os import path
from pathlib import Path
from ua_parser import user_agent_parser
from re import findall
from flask import request, make_response, abort
from flask import request, abort
from flask_babel import _
from werkzeug.utils import secure_filename
@@ -325,42 +324,6 @@ def is_safe_path(file_name):
return {"status": True, "msg": ""}
def upload_with_dropzonejs(image_dir):
"""
Takes (str) image_dir which is the path to the image dir to store files.
Opens a stream to transfer a file via the embedded dropzonejs library.
"""
log = logging.getLogger("pydrop")
file_object = request.files["file"]
file_name = secure_filename(file_object.filename)
save_path = path.join(image_dir, file_name)
current_chunk = int(request.form["dzchunkindex"])
# Makes sure not to overwrite an existing file,
# but continues writing to a file transfer in progress
if path.exists(save_path) and current_chunk == 0:
return make_response(_("The file already exists!"), 400)
try:
with open(save_path, "ab") as save:
save.seek(int(request.form["dzchunkbyteoffset"]))
save.write(file_object.stream.read())
except OSError:
log.exception("Could not write to file")
return make_response(_("Unable to write the file to disk!"), 500)
total_chunks = int(request.form["dztotalchunkcount"])
if current_chunk + 1 == total_chunks:
# Validate the resulting file size after writing the last chunk
if path.getsize(save_path) != int(request.form["dztotalfilesize"]):
log.error("File size mismatch between the original file and transferred file.")
return make_response(_("Transferred file corrupted!"), 500)
return make_response(_("File upload successful!"), 200)
def browser_supports_modern_themes():
"""
Determines if the browser supports the HTML/CSS/JS features used in non-legacy themes.