def patch(self, document_guid=None): if document_guid is None: return self.create_error_payload( 400, 'Must specify document GUID in PATCH'), 400 file_path = cache.get(FILE_UPLOAD_PATH(document_guid)) if file_path is None or not os.path.lexists(file_path): return self.create_error_payload( 404, 'PATCH sent for a upload that does not exist'), 404 request_offset = int(request.headers.get('Upload-Offset', 0)) file_offset = cache.get(FILE_UPLOAD_OFFSET(document_guid)) if request_offset != file_offset: return self.create_error_payload( 409, "Offset in request does not match uploaded file's offest"), 409 chunk_size = request.headers.get('Content-Length') if chunk_size is None: return self.create_error_payload( 400, 'No Content-Length header in request'), 400 chunk_size = int(chunk_size) new_offset = file_offset + chunk_size file_size = cache.get(FILE_UPLOAD_SIZE(document_guid)) if new_offset > file_size: return self.create_error_payload( 413, 'The uploaded chunk would put the file above its declared file size.' ), 413 try: with open(file_path, "r+b") as f: f.seek(file_offset) f.write(request.data) except IOError as e: return self.create_error_payload(500, 'Unable to write to file'), 500 if new_offset == file_size: # File transfer complete. doc = DocumentManager.find_by_document_manager_guid(document_guid) doc.upload_completed_date = datetime.now() doc.save() cache.delete(FILE_UPLOAD_SIZE(document_guid)) cache.delete(FILE_UPLOAD_OFFSET(document_guid)) cache.delete(FILE_UPLOAD_PATH(document_guid)) else: # File upload still in progress cache.set(FILE_UPLOAD_OFFSET(document_guid), new_offset, TIMEOUT_24_HOURS) response = make_response("", 204) response.headers['Tus-Resumable'] = TUS_API_VERSION response.headers['Tus-Version'] = TUS_API_SUPPORTED_VERSIONS response.headers['Upload-Offset'] = new_offset response.headers[ 'Access-Control-Expose-Headers'] = "Tus-Resumable,Tus-Version,Upload-Offset" return response
def head(self, document_guid): if document_guid is None: return self.create_error_payload(400, 'Must specify document GUID in HEAD'), 400 file_path = cache.get(FILE_UPLOAD_PATH(document_guid)) if file_path is None or not os.path.lexists(file_path): return self.create_error_payload(404, 'File does not exist'), 404 response = make_response("", 200) response.headers['Tus-Resumable'] = TUS_API_VERSION response.headers['Tus-Version'] = TUS_API_SUPPORTED_VERSIONS response.headers['Upload-Offset'] = cache.get(FILE_UPLOAD_OFFSET(document_guid)) response.headers['Upload-Length'] = cache.get(FILE_UPLOAD_SIZE(document_guid)) response.headers['Cache-Control'] = 'no-store' response.headers[ 'Access-Control-Expose-Headers'] = "Tus-Resumable,Tus-Version,Upload-Offset,Upload-Length,Cache-Control" return response
def post(self): if request.headers.get('Tus-Resumable') is None: raise BadRequest( 'Received file upload for unsupported file transfer protocol') file_size = request.headers.get('Upload-Length') max_file_size = current_app.config["MAX_CONTENT_LENGTH"] if not file_size: raise BadRequest('Received file upload of unspecified size') file_size = int(file_size) if file_size > max_file_size: raise RequestEntityTooLarge( f'The maximum file upload size is {max_file_size/1024/1024}MB.' ) data = self.parser.parse_args() filename = data.get('filename') if not filename: raise BadRequest('File name cannot be empty') if filename.endswith(FORBIDDEN_FILETYPES): raise BadRequest('File type is forbidden') document_guid = str(uuid.uuid4()) base_folder = current_app.config['UPLOADED_DOCUMENT_DEST'] folder = data.get('folder') folder = os.path.join(base_folder, folder) file_path = os.path.join(folder, document_guid) pretty_folder = data.get('pretty_folder') pretty_path = os.path.join(base_folder, pretty_folder, filename) try: if not os.path.exists(folder): os.makedirs(folder) with open(file_path, "wb") as f: f.seek(file_size - 1) f.write(b"\0") except IOError as e: raise InternalServerError('Unable to create file') cache.set(FILE_UPLOAD_SIZE(document_guid), file_size, TIMEOUT_24_HOURS) cache.set(FILE_UPLOAD_OFFSET(document_guid), 0, TIMEOUT_24_HOURS) cache.set(FILE_UPLOAD_PATH(document_guid), file_path, TIMEOUT_24_HOURS) document_info = DocumentManager( document_guid=document_guid, full_storage_path=file_path, upload_started_date=datetime.utcnow(), file_display_name=filename, path_display_name=pretty_path, ) document_info.save() response = make_response(jsonify(document_manager_guid=document_guid), 201) response.headers['Tus-Resumable'] = TUS_API_VERSION response.headers['Tus-Version'] = TUS_API_SUPPORTED_VERSIONS response.headers[ 'Location'] = f'{current_app.config["DOCUMENT_MANAGER_URL"]}/document-manager/{document_guid}' response.headers['Upload-Offset'] = 0 response.headers[ 'Access-Control-Expose-Headers'] = "Tus-Resumable,Tus-Version,Location,Upload-Offset" response.autocorrect_location_header = False return response