From 6397d9c9a3ab055feee7867179bd83ca94d75391 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Tue, 14 Jun 2022 19:03:56 -0700 Subject: [PATCH] Add a Copy image file flow to the Web UI. (#760) * Add a Copy image file flow to the Web UI. * Introduce a generic file creation message and use that consistently. * Clarify code comment --- python/common/src/rascsi/file_cmds.py | 56 +++++++++++++++++++++--- python/common/src/rascsi/return_codes.py | 8 ++-- python/web/src/return_code_mapper.py | 7 +-- python/web/src/templates/index.html | 5 +++ python/web/src/web.py | 32 ++++++++++++++ 5 files changed, 91 insertions(+), 17 deletions(-) diff --git a/python/common/src/rascsi/file_cmds.py b/python/common/src/rascsi/file_cmds.py index bfc71af6..50d8f2cd 100644 --- a/python/common/src/rascsi/file_cmds.py +++ b/python/common/src/rascsi/file_cmds.py @@ -11,6 +11,7 @@ from re import escape, findall from time import time from subprocess import run, CalledProcessError from json import dump, load +from shutil import copyfile import requests @@ -180,6 +181,25 @@ class FileCmds: result.ParseFromString(data) return {"status": result.status, "msg": result.msg} + def copy_image(self, file_name, new_file_name): + """ + Takes (str) file_name, (str) new_file_name + Sends a COPY_IMAGE command to the server + Returns (dict) with (bool) status and (str) msg + """ + command = proto.PbCommand() + command.operation = proto.PbOperation.COPY_IMAGE + command.params["token"] = self.ractl.token + command.params["locale"] = self.ractl.locale + + command.params["from"] = file_name + command.params["to"] = new_file_name + + data = self.sock_cmd.send_pb_command(command.SerializeToString()) + result = proto.PbResult() + result.ParseFromString(data) + return {"status": result.status, "msg": result.msg} + # noinspection PyMethodMayBeStatic def delete_file(self, file_path): """ @@ -224,6 +244,28 @@ class FileCmds: "parameters": parameters, } + # noinspection PyMethodMayBeStatic + def copy_file(self, file_path, target_path): + """ + Takes (str) file_path and (str) target_path + Returns (dict) with (bool) status and (str) msg + """ + parameters = { + "target_path": target_path + } + if os.path.exists(PurePath(target_path).parent): + copyfile(file_path, target_path) + return { + "status": True, + "return_code": ReturnCodes.WRITEFILE_SUCCESS, + "parameters": parameters, + } + return { + "status": False, + "return_code": ReturnCodes.WRITEFILE_UNABLE_TO_WRITE, + "parameters": parameters, + } + def unzip_file(self, file_name, member=False, members=False): """ Takes (str) file_name, optional (str) member, optional (list) of (str) members @@ -404,11 +446,11 @@ class FileCmds: indent=4 ) parameters = { - "file_name": file_name + "target_path": file_name } return { "status": True, - "return_code": ReturnCodes.WRITECONFIG_SUCCESS, + "return_code": ReturnCodes.WRITEFILE_SUCCESS, "parameters": parameters, } except (IOError, ValueError, EOFError, TypeError) as error: @@ -423,7 +465,7 @@ class FileCmds: } return { "status": False, - "return_code": ReturnCodes.WRITECONFIG_COULD_NOT_WRITE, + "return_code": ReturnCodes.WRITEFILE_COULD_NOT_WRITE, "parameters": parameters, } @@ -515,11 +557,11 @@ class FileCmds: with open(file_path, "w") as json_file: dump(conf, json_file, indent=4) parameters = { - "file_path": file_path + "target_path": file_path } return { "status": True, - "return_code": ReturnCodes.WRITEDRIVEPROPS_SUCCESS, + "return_code": ReturnCodes.WRITEFILE_SUCCESS, "parameters": parameters, } except (IOError, ValueError, EOFError, TypeError) as error: @@ -530,11 +572,11 @@ class FileCmds: logging.error("Could not write to file: %s", file_path) self.delete_file(file_path) parameters = { - "file_path": file_path + "target_path": file_path } return { "status": False, - "return_code": ReturnCodes.WRITEDRIVEPROPS_COULD_NOT_WRITE, + "return_code": ReturnCodes.WRITEFILE_COULD_NOT_WRITE, "parameters": parameters, } diff --git a/python/common/src/rascsi/return_codes.py b/python/common/src/rascsi/return_codes.py index 9b7d063c..168ea75e 100644 --- a/python/common/src/rascsi/return_codes.py +++ b/python/common/src/rascsi/return_codes.py @@ -12,13 +12,11 @@ class ReturnCodes: RENAMEFILE_UNABLE_TO_MOVE = 11 DOWNLOADFILETOISO_SUCCESS = 20 DOWNLOADTODIR_SUCCESS = 30 - WRITECONFIG_SUCCESS = 40 - WRITECONFIG_COULD_NOT_WRITE = 41 + WRITEFILE_SUCCESS = 40 + WRITEFILE_COULD_NOT_WRITE = 41 READCONFIG_SUCCESS = 50 READCONFIG_COULD_NOT_READ = 51 - READCONFIG_INVALID_CONFIG_FILE_FORMAT = 51 - WRITEDRIVEPROPS_SUCCESS = 60 - WRITEDRIVEPROPS_COULD_NOT_WRITE = 61 + READCONFIG_INVALID_CONFIG_FILE_FORMAT = 52 READDRIVEPROPS_SUCCESS = 70 READDRIVEPROPS_COULD_NOT_READ = 71 ATTACHIMAGE_COULD_NOT_ATTACH = 80 diff --git a/python/web/src/return_code_mapper.py b/python/web/src/return_code_mapper.py index d42fad93..6b596f1c 100644 --- a/python/web/src/return_code_mapper.py +++ b/python/web/src/return_code_mapper.py @@ -16,15 +16,12 @@ class ReturnCodeMapper: ReturnCodes.DOWNLOADFILETOISO_SUCCESS: _("Created CD-ROM ISO image with " "arguments \"%(value)s\""), ReturnCodes.DOWNLOADTODIR_SUCCESS: _("%(file_name)s downloaded to %(save_dir)s"), - ReturnCodes.WRITECONFIG_SUCCESS: _("Saved configuration file to %(file_name)s"), - ReturnCodes.WRITECONFIG_COULD_NOT_WRITE: _("Could not write to file: %(file_name)s"), + ReturnCodes.WRITEFILE_SUCCESS: _("File created: %(target_path)s"), + ReturnCodes.WRITEFILE_COULD_NOT_WRITE: _("Could not create file: %(target_path)s"), ReturnCodes.READCONFIG_SUCCESS: _("Loaded configurations from: %(file_name)s"), ReturnCodes.READCONFIG_COULD_NOT_READ: _("Could not read configuration " "file: %(file_name)s"), ReturnCodes.READCONFIG_INVALID_CONFIG_FILE_FORMAT: _("Invalid configuration file format"), - ReturnCodes.WRITEDRIVEPROPS_SUCCESS: _("Created properties file: %(file_path)s"), - ReturnCodes.WRITEDRIVEPROPS_COULD_NOT_WRITE: _("Could not write to properties " - "file: %(file_path)s"), ReturnCodes.READDRIVEPROPS_SUCCESS: _("Read properties from file: %(file_path)s"), ReturnCodes.READDRIVEPROPS_COULD_NOT_READ: _("Could not read properties from " "file: %(file_path)s"), diff --git a/python/web/src/templates/index.html b/python/web/src/templates/index.html index 269e1d58..49c2d096 100644 --- a/python/web/src/templates/index.html +++ b/python/web/src/templates/index.html @@ -283,6 +283,11 @@ +
+ + + +
diff --git a/python/web/src/web.py b/python/web/src/web.py index f551af28..81fa3330 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -912,6 +912,38 @@ def rename(): return redirect(url_for("index")) +@APP.route("/files/copy", methods=["POST"]) +@login_required +def copy(): + """ + Creates a copy of a specified file in the images dir + """ + file_name = request.form.get("file_name") + new_file_name = request.form.get("copy_file_name") + + process = file_cmd.copy_image(file_name, new_file_name) + if process["status"]: + flash(_("Copy of image file saved as: %(file_name)s", file_name=new_file_name)) + else: + flash(process["msg"], "error") + return redirect(url_for("index")) + + # Create a copy of the drive properties file, if it exists + prop_file_path = f"{CFG_DIR}/{file_name}.{PROPERTIES_SUFFIX}" + new_prop_file_path = f"{CFG_DIR}/{new_file_name}.{PROPERTIES_SUFFIX}" + if Path(prop_file_path).is_file(): + process = file_cmd.copy_file(prop_file_path, new_prop_file_path) + process = ReturnCodeMapper.add_msg(process) + if process["status"]: + flash(process["msg"]) + return redirect(url_for("index")) + + flash(process["msg"], "error") + return redirect(url_for("index")) + + return redirect(url_for("index")) + + @APP.route("/files/unzip", methods=["POST"]) @login_required def unzip():