From f0fa1295a7c2a6d38def9997786e0f15e8d0b879 Mon Sep 17 00:00:00 2001 From: Arksine Date: Wed, 22 Jul 2020 07:34:21 -0400 Subject: [PATCH] file_manager: handle file upload processing This implentation allows for uploads to different local paths by specifying a "root" argument in the form. Signed-off-by: Eric Callahan --- moonraker/app.py | 72 ++++----------------------- moonraker/plugins/file_manager.py | 81 ++++++++++++++++++++++++++++--- 2 files changed, 83 insertions(+), 70 deletions(-) diff --git a/moonraker/app.py b/moonraker/app.py index da255d2..eee1a97 100644 --- a/moonraker/app.py +++ b/moonraker/app.py @@ -202,10 +202,8 @@ class MoonrakerApp: 'path': file_path, 'methods': methods, 'op_check_cb': op_check_cb} self.mutable_router.add_handler(pattern, FileRequestHandler, params) - def register_upload_handler(self, pattern, upload_path, op_check_cb=None): - params = { - 'server': self.server, 'auth': self.auth, - 'path': upload_path, 'op_check_cb': op_check_cb} + def register_upload_handler(self, pattern): + params = {'server': self.server, 'auth': self.auth} self.mutable_router.add_handler(pattern, FileUploadHandler, params) def remove_handler(self, endpoint): @@ -345,70 +343,18 @@ class FileRequestHandler(AuthorizedFileHandler): self.finish({'result': filename}) class FileUploadHandler(AuthorizedRequestHandler): - def initialize(self, server, auth, path, op_check_cb=None,): + def initialize(self, server, auth): super(FileUploadHandler, self).initialize(server, auth) - self.op_check_cb = op_check_cb - self.file_path = path async def post(self): - start_print = False - dir_path = "" - print_args = self.request.arguments.get('print', []) - path_args = self.request.arguments.get('path', []) - if print_args: - start_print = print_args[0].decode().lower() == "true" - if path_args: - dir_path = path_args[0].decode().lstrip("/") - upload = self.get_file() - filename = "_".join(upload['filename'].strip().split()).lstrip("/") - if dir_path: - filename = os.path.join(dir_path, filename) - full_path = os.path.join(self.file_path, filename) - # Make sure the file isn't currently loaded - ongoing = False - if self.op_check_cb is not None: - try: - ongoing = await self.op_check_cb(full_path) - except ServerError as e: - if e.status_code == 403: - raise tornado.web.HTTPError( - 403, "File is loaded, upload not permitted") - else: - # Couldn't reach Klippy, so it should be safe - # to permit the upload but not start - start_print = False - - # Don't start if another print is currently in progress - start_print = start_print and not ongoing + file_manager = self.server.lookup_plugin('file_manager') try: - if dir_path: - os.makedirs(os.path.dirname(full_path), exist_ok=True) - with open(full_path, 'wb') as fh: - fh.write(upload['body']) - self.server.notify_filelist_changed(filename, 'added') - except Exception: - raise tornado.web.HTTPError(500, "Unable to save file") - if start_print: - # Make a Klippy Request to "Start Print" - gcode_apis = self.server.lookup_plugin('gcode_apis') - try: - await gcode_apis.gcode_start_print( - self.request.path, 'POST', {'filename': filename}) - except ServerError: - # Attempt to start print failed - start_print = False - self.finish({'result': filename, 'print_started': start_print}) + result = await file_manager.process_file_upload(self.request) + except ServerError as e: + raise tornado.web.HTTPError( + e.status_code, str(e)) + self.finish(result) - def get_file(self): - # File uploads must have a single file request - if len(self.request.files) != 1: - raise tornado.web.HTTPError( - 400, "Bad Request, can only process a single file upload") - f_list = list(self.request.files.values())[0] - if len(f_list) != 1: - raise tornado.web.HTTPError( - 400, "Bad Request, can only process a single file upload") - return f_list[0] class EmulateOctoprintHandler(AuthorizedRequestHandler): def get(self): diff --git a/moonraker/plugins/file_manager.py b/moonraker/plugins/file_manager.py index 4950555..9a5fb9d 100644 --- a/moonraker/plugins/file_manager.py +++ b/moonraker/plugins/file_manager.py @@ -38,17 +38,14 @@ class FileManager: self.server.register_endpoint( "/server/files/copy", "file_copy", ['POST'], self._handle_file_move_copy) + # Register APIs to handle file uploads + self.server.register_upload_handler("/server/files/upload") + self.server.register_upload_handler("/api/files/local") def _register_static_files(self, gcode_path): self.server.register_static_file_handler( '/server/files/gcodes/', gcode_path, can_delete=True, op_check_cb=self._handle_operation_check) - self.server.register_upload_handler( - '/server/files/upload', gcode_path, - op_check_cb=self._handle_operation_check) - self.server.register_upload_handler( - '/api/files/local', gcode_path, - op_check_cb=self._handle_operation_check) def load_config(self, config): sd = config.get('sd_path', None) @@ -156,7 +153,8 @@ class FileManager: if source is None: raise self.server.error("File move/copy request issing source") if destination is None: - raise self.server.error("File move/copy request missing destination") + raise self.server.error( + "File move/copy request missing destination") source_base, source_path = self._convert_path(source) dest_base, dest_path = self._convert_path(destination) if source_base != "gcodes" or dest_base != "gcodes": @@ -168,6 +166,7 @@ class FileManager: # make sure the destination is not in use if os.path.exists(dest_path): await self._handle_operation_check(dest_path) + action = "" if path == "/server/files/move": # if moving the file, make sure the source is not in use await self._handle_operation_check(source_path) @@ -273,6 +272,74 @@ class FileManager: ioloop.spawn_callback(self._update_metadata) return dict(new_list) + async def process_file_upload(self, request): + start_print = print_ongoing = False + dir_path = "" + # lookup root file path + root_args = request.arguments.get('root', ['gcodes']) + root = root_args[0].strip() + file_path = self.file_paths.get(root, None) + if file_path is None: + raise self.server.error(400, "Unknown root path") + # check relative path + path_args = request.arguments.get('path', []) + if path_args: + dir_path = path_args[0].decode().lstrip("/") + # check if print should be started after a "gcodes" upload + if root == "gcodes": + print_args = request.arguments.get('print', []) + if print_args: + start_print = print_args[0].decode().lower() == "true" + # fetch the upload from the request + if len(request.files) != 1: + raise self.server.error( + 400, "Bad Request, can only process a single file upload") + f_list = list(request.files.values())[0] + if len(f_list) != 1: + raise self.server.error( + 400, "Bad Request, can only process a single file upload") + upload = f_list[0] + filename = "_".join(upload['filename'].strip().split()).lstrip("/") + if dir_path: + filename = os.path.join(dir_path, filename) + full_path = os.path.join(file_path, filename) + # Verify that the operation can be done if attempting to upload a gcode + if root == 'gcodes': + try: + print_ongoing = await self._handle_operation_check(full_path) + except self.server.error as e: + if e.status_code == 403: + raise self.server.error( + 403, "File is loaded, upload not permitted") + else: + # Couldn't reach Klippy, so it should be safe + # to permit the upload but not start + start_print = False + + # Don't start if another print is currently in progress + start_print = start_print and not print_ongoing + try: + if dir_path: + os.makedirs(os.path.dirname(full_path), exist_ok=True) + with open(full_path, 'wb') as fh: + fh.write(upload['body']) + except Exception: + raise self.server.error(500, "Unable to save file") + if start_print: + # Make a Klippy Request to "Start Print" + gcode_apis = self.server.lookup_plugin('gcode_apis') + try: + await gcode_apis.gcode_start_print( + request.path, 'POST', {'filename': filename}) + except self.server.error: + # Attempt to start print failed + start_print = False + if root == 'gcodes': + self.server.notify_filelist_changed(filename, 'added') + return {'result': filename, 'print_started': start_print} + else: + return {'result': filename} + def get_file_list(self, format_list=False, base='gcodes'): try: filelist = self._update_file_list(base)