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 <arksine.code@gmail.com>
This commit is contained in:
Arksine 2020-07-22 07:34:21 -04:00 committed by Eric Callahan
parent 2ae4034b97
commit f0fa1295a7
2 changed files with 83 additions and 70 deletions

View File

@ -202,10 +202,8 @@ class MoonrakerApp:
'path': file_path, 'methods': methods, 'op_check_cb': op_check_cb} 'path': file_path, 'methods': methods, 'op_check_cb': op_check_cb}
self.mutable_router.add_handler(pattern, FileRequestHandler, params) self.mutable_router.add_handler(pattern, FileRequestHandler, params)
def register_upload_handler(self, pattern, upload_path, op_check_cb=None): def register_upload_handler(self, pattern):
params = { params = {'server': self.server, 'auth': self.auth}
'server': self.server, 'auth': self.auth,
'path': upload_path, 'op_check_cb': op_check_cb}
self.mutable_router.add_handler(pattern, FileUploadHandler, params) self.mutable_router.add_handler(pattern, FileUploadHandler, params)
def remove_handler(self, endpoint): def remove_handler(self, endpoint):
@ -345,70 +343,18 @@ class FileRequestHandler(AuthorizedFileHandler):
self.finish({'result': filename}) self.finish({'result': filename})
class FileUploadHandler(AuthorizedRequestHandler): class FileUploadHandler(AuthorizedRequestHandler):
def initialize(self, server, auth, path, op_check_cb=None,): def initialize(self, server, auth):
super(FileUploadHandler, self).initialize(server, auth) super(FileUploadHandler, self).initialize(server, auth)
self.op_check_cb = op_check_cb
self.file_path = path
async def post(self): async def post(self):
start_print = False file_manager = self.server.lookup_plugin('file_manager')
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
try: try:
if dir_path: result = await file_manager.process_file_upload(self.request)
os.makedirs(os.path.dirname(full_path), exist_ok=True) except ServerError as e:
with open(full_path, 'wb') as fh: raise tornado.web.HTTPError(
fh.write(upload['body']) e.status_code, str(e))
self.server.notify_filelist_changed(filename, 'added') self.finish(result)
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})
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): class EmulateOctoprintHandler(AuthorizedRequestHandler):
def get(self): def get(self):

View File

@ -38,17 +38,14 @@ class FileManager:
self.server.register_endpoint( self.server.register_endpoint(
"/server/files/copy", "file_copy", ['POST'], "/server/files/copy", "file_copy", ['POST'],
self._handle_file_move_copy) 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): def _register_static_files(self, gcode_path):
self.server.register_static_file_handler( self.server.register_static_file_handler(
'/server/files/gcodes/', gcode_path, can_delete=True, '/server/files/gcodes/', gcode_path, can_delete=True,
op_check_cb=self._handle_operation_check) 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): def load_config(self, config):
sd = config.get('sd_path', None) sd = config.get('sd_path', None)
@ -156,7 +153,8 @@ class FileManager:
if source is None: if source is None:
raise self.server.error("File move/copy request issing source") raise self.server.error("File move/copy request issing source")
if destination is None: 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) source_base, source_path = self._convert_path(source)
dest_base, dest_path = self._convert_path(destination) dest_base, dest_path = self._convert_path(destination)
if source_base != "gcodes" or dest_base != "gcodes": if source_base != "gcodes" or dest_base != "gcodes":
@ -168,6 +166,7 @@ class FileManager:
# make sure the destination is not in use # make sure the destination is not in use
if os.path.exists(dest_path): if os.path.exists(dest_path):
await self._handle_operation_check(dest_path) await self._handle_operation_check(dest_path)
action = ""
if path == "/server/files/move": if path == "/server/files/move":
# if moving the file, make sure the source is not in use # if moving the file, make sure the source is not in use
await self._handle_operation_check(source_path) await self._handle_operation_check(source_path)
@ -273,6 +272,74 @@ class FileManager:
ioloop.spawn_callback(self._update_metadata) ioloop.spawn_callback(self._update_metadata)
return dict(new_list) 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'): def get_file_list(self, format_list=False, base='gcodes'):
try: try:
filelist = self._update_file_list(base) filelist = self._update_file_list(base)