"""Module for generic files endpoints.""" import traceback from pathlib import Path from flask import request, jsonify, Blueprint, current_app as app from .chunks import chunk_name, chunks_directory files = Blueprint("files", __name__) @files.route("/upload/resumable", methods=["GET"]) def resumable_upload_get(): """Used for checking whether **ALL** chunks have been uploaded.""" fileid = request.args.get("resumableIdentifier", type=str) or "" filename = request.args.get("resumableFilename", type=str) or "" chunk = request.args.get("resumableChunkNumber", type=int) or 0 if not(fileid or filename or chunk): return jsonify({ "message": "At least one required query parameter is missing.", "error": "BadRequest", "statuscode": 400 }), 400 if Path(chunks_directory(fileid), chunk_name(filename, chunk)).exists(): return "OK" return jsonify({ "message": f"Chunk {chunk} was not found.", "error": "NotFound", "statuscode": 404 }), 404 def __merge_chunks__(targetfile: Path, chunkpaths: tuple[Path, ...]) -> Path: """Merge the chunks into a single file.""" with open(targetfile, "ab") as _target: for chunkfile in chunkpaths: with open(chunkfile, "rb") as _chunkdata: _target.write(_chunkdata.read()) chunkfile.unlink() return targetfile @files.route("/upload/resumable", methods=["POST"]) def resumable_upload_post(): """Do the actual chunks upload here.""" _totalchunks = request.form.get("resumableTotalChunks", type=int) or 0 _chunk = request.form.get("resumableChunkNumber", default=1, type=int) _uploadfilename = request.form.get( "resumableFilename", default="", type=str) or "" _fileid = request.form.get( "resumableIdentifier", default="", type=str) or "" _targetfile = Path(app.config["UPLOAD_FOLDER"], _fileid) if _targetfile.exists(): return jsonify({ "message": ( "A file with a similar unique identifier has previously been " "uploaded and possibly is/has being/been processed."), "error": "BadRequest", "statuscode": 400 }), 400 try: chunks_directory(_fileid).mkdir(exist_ok=True, parents=True) request.files["file"].save(Path(chunks_directory(_fileid), chunk_name(_uploadfilename, _chunk))) # Check whether upload is complete chunkpaths = tuple( Path(chunks_directory(_fileid), chunk_name(_uploadfilename, _achunk)) for _achunk in range(1, _totalchunks+1)) if all(_file.exists() for _file in chunkpaths): # merge_files and clean up chunks __merge_chunks__(_targetfile, chunkpaths) chunks_directory(_fileid).rmdir() return jsonify({ "uploaded-file": _targetfile.name, "message": "File was uploaded successfully!", "statuscode": 200 }), 200 return jsonify({ "message": "Some chunks were not uploaded!", "error": "ChunksUploadError", "error-description": "Some chunks were not uploaded!" }) except Exception as exc:# pylint: disable=[broad-except] msg = "Error processing uploaded file chunks." app.logger.error(msg, exc_info=True, stack_info=True) return jsonify({ "message": msg, "error": type(exc).__name__, "error-description": " ".join(str(arg) for arg in exc.args), "error-trace": traceback.format_exception(exc) }), 500