import json import os.path import traceback from typing import List, Dict, Optional import simple_websocket from mod.base import json_response from mod.project.node.dbutil import FileTransfer, FileTransferTask, FileTransferDB, ServerNodeDB from mod.project.node.nodeutil import ServerNode, LocalNode, LPanelNode from mod.project.node.filetransfer import task_running_log, wait_running import public class main(): log_dir = "{}/logs/node_file_transfers".format(public.get_panel_path()) if not os.path.exists(log_dir): os.makedirs(log_dir) @staticmethod def file_upload(args): node_id = args.get('node_id', -1) if node_id == -1: from YakPanel import request node_id = request.form.get('node_id', 0) if not node_id: return public.return_message(-1, 0,"node_id is null") if isinstance(node_id, str): try: node_id = int(node_id) except: return public.return_message(-1, 0,"node_id is null") node = ServerNode.new_by_id(node_id) if not node: return public.return_message(-1, 0,"node not exists") return node.upload_proxy() @staticmethod def file_download(args): node_id = args.get('node_id', 0) if not node_id: return public.return_message(-1, 0,"node_id is null") filename = args.get('filename/s', "") if not filename: return jpublic.return_message(-1, 0,"The filename parameter cannot be empty") if isinstance(node_id, str): try: node_id = int(node_id) except: return public.return_message(-1, 0,"node_id is null") node = ServerNode.new_by_id(node_id) if not node: return public.return_message(-1, 0,"node not exists") return node.download_proxy(filename) @staticmethod def dir_walk(get): path = get.get('path/s', "") if not path: return public.return_message(-1, 0,"The path parameter cannot be empty") res_list, err = LocalNode().dir_walk(path) if err: return public.return_message(-1, 0,err) return res_list @classmethod def create_filetransfer_task(cls, get): ft_db = FileTransferDB() task_data, err = ft_db.get_last_task() if err: return public.return_message(-1, 0,err) if task_data and task_data["status"] not in ("complete", "failed"): return public.return_message(-1, 0,"There are ongoing tasks on the current node, please wait for them to complete before submitting") public.set_module_logs("nodes_create_filetransfer_9", "create_filetransfer") source_node_id = get.get('source_node_id/d', -1) target_node_id = get.get('target_node_id/d', -1) source_path_list = get.get('source_path_list/s', "") target_path = get.get('target_path/s', "") default_mode = get.get('default_mode/s', "cover") if default_mode not in ("cover", "ignore", "rename"): return public.return_message(-1, 0,"Default mode parameter error") if source_node_id == target_node_id: return public.return_message(-1, 0,"The source node and target node cannot be the same") if source_node_id == -1 or target_node_id == -1: return public.return_message(-1, 0,"The source or destination node cannot be empty") try: source_path_list = json.loads(source_path_list) except: return public.return_message(-1, 0,"Error in the parameter 'sourcew_path_ist'") keys = ("path", "size", "is_dir") for items in source_path_list: if not all(item in keys for item in items.keys()): return public.return_message(-1, 0,"Error in the parameter 'sourcew_path_ist'") if not (isinstance(items["path"], str) and isinstance(items["is_dir"], bool) and isinstance(items["size"], int)): return public.return_message(-1, 0,"Error in the parameter 'sourcew_path_ist'") if not target_path: return public.return_message(-1, 0,"The target_cath parameter cannot be empty") node_db = ServerNodeDB() if source_node_id == 0: src_node = node_db.get_local_node() else: src_node = node_db.get_node_by_id(source_node_id) if not src_node: return public.return_message(-1, 0,"The source node does not exist") if target_node_id == 0: target_node = node_db.get_local_node() else: target_node = node_db.get_node_by_id(target_node_id) if not target_node: return public.return_message(-1, 0,"The target node does not exist") if src_node["id"] == target_node["id"]: return public.return_message(-1, 0,"The source node and target node cannot be the same") # public.print_log("src_node:", src_node, "target_node:", target_node) real_create_res: Optional[dict] = None # 实际上创建的结果,创建的任务不在本地时使用 if src_node["api_key"] == src_node["app_key"] == "local": return cls._create_filetransfer_task( source_node={ "name": "local", }, target_node={ "name": "{}({})".format(target_node["remarks"], target_node["server_ip"]), "address": target_node["address"], "api_key": target_node["api_key"], "app_key": target_node["app_key"], "node_id": target_node_id, "lpver": target_node["lpver"] }, source_path_list=source_path_list, target_path=target_path, created_by="local", default_mode=default_mode, ) elif target_node["api_key"] == target_node["app_key"] == "local": return cls._create_filetransfer_task( source_node={ "name": "{}({})".format(src_node["remarks"], src_node["server_ip"]), "address": src_node["address"], "api_key": src_node["api_key"], "app_key": src_node["app_key"], "node_id": source_node_id, "lpver": src_node["lpver"] }, target_node={ "name": "local", }, source_path_list=source_path_list, target_path=target_path, created_by="local", default_mode=default_mode, ) elif src_node["lpver"]: if target_node["lpver"]: return public.return_message(-1, 0,"Cannot support file transfer between 1panel nodes") # 源节点是1panel时,只能下载去目标节点操作 if target_node["api_key"] == target_node["app_key"] == "local": return cls._create_filetransfer_task( source_node={ "name": "{}".format(target_node["remarks"]) + ("({})".format(target_node["server_ip"]) if target_node["server_ip"] else ""), "address": src_node["address"], "api_key": src_node["api_key"], "app_key": "", "node_id": source_node_id, "lpver": src_node["lpver"] }, target_node={ "name": "local", }, source_path_list=source_path_list, target_path=target_path, created_by="local", default_mode=default_mode, ) else: srv = ServerNode(target_node["address"], target_node["api_key"], target_node["app_key"]) real_create_res = srv.node_create_filetransfer_task( source_node={ "name": "{}".format(target_node["remarks"]) + ("({})".format(target_node["server_ip"]) if target_node["server_ip"] else ""), "address": src_node["address"], "api_key": src_node["api_key"], "app_key": "", "node_id": source_node_id, "lpver": src_node["lpver"] }, target_node={ "name": "local", }, source_path_list=source_path_list, target_path=target_path, created_by="{}({})".format(public.GetConfigValue("title"), public.get_server_ip()), default_mode=default_mode ) else: # 都是YakPanel 节点的情况下 srv = ServerNode(src_node["address"], src_node["api_key"], src_node["app_key"]) if srv.filetransfer_version_check(): srv = ServerNode(target_node["address"], target_node["api_key"], target_node["app_key"]) res = srv.filetransfer_version_check() if res: return public.return_message(-1, 0,"{} Node check error:".format(target_node["remarks"]) + res) real_create_res = srv.node_create_filetransfer_task( source_node={ "name": "{}".format(target_node["remarks"]) + ("({})".format(target_node["server_ip"]) if target_node["server_ip"] else ""), "address": src_node["address"], "api_key": src_node["api_key"], "app_key": src_node["app_key"], "node_id": source_node_id, "lpver": src_node["lpver"] }, target_node={ "name": "local", }, source_path_list=source_path_list, target_path=target_path, created_by="{}({})".format(public.GetConfigValue("title"), public.get_server_ip()), default_mode=default_mode, ) else: real_create_res = srv.node_create_filetransfer_task( source_node={ "name": "local", }, target_node={ "name": "{}".format(target_node["remarks"]) + ("({})".format(target_node["server_ip"]) if target_node["server_ip"] else ""), "address": target_node["address"], "api_key": target_node["api_key"], "app_key": target_node["app_key"], "node_id": target_node_id, "lpver": target_node["lpver"] }, source_path_list=source_path_list, target_path=target_path, created_by="{}({})".format(public.GetConfigValue("title"), public.get_server_ip()), default_mode=default_mode, ) if not real_create_res["status"]: return public.return_message(-1, 0,real_create_res["msg"]) tt_task_id = real_create_res["data"]["task_id"] db = FileTransferDB() tt = FileTransferTask( source_node={"node_id": source_node_id}, target_node={"node_id": target_node_id}, source_path_list=source_path_list, target_path=target_path, task_action=real_create_res["data"]["task_action"], status="running", created_by="local", default_mode=default_mode, target_task_id=tt_task_id, is_source_node=node_db.is_local_node(source_node_id), is_target_node=node_db.is_local_node(target_node_id), ) db.create_task(tt) return public.return_message(-1, 0,tt.to_dict()) @classmethod def node_create_filetransfer_task(cls, get): from YakPanel import g if not g.api_request: return public.return_message(-1, 0,"Unable to activate") source_node = get.get("source_node/s", "") target_node = get.get("target_node/s", "") source_path_list = get.get("source_path_list/s", "") target_path = get.get("target_path/s", "") created_by = get.get("created_by/s", "") default_mode = get.get("default_mode/s", "") try: source_node = json.loads(source_node) target_node = json.loads(target_node) source_path_list = json.loads(source_path_list) except Exception: return public.return_message(-1, 0,"Parameter error") if not target_path or not created_by or not default_mode or not source_node or not target_node or not source_path_list: return public.return_message(-1, 0,"Parameter loss") ft_db = FileTransferDB() task_data, err = ft_db.get_last_task() if err: return public.return_message(-1, 0,err) if task_data and task_data["status"] not in ("complete", "failed"): return public.return_message(-1, 0,"There is an ongoing task on the node, please wait for it to complete before submitting") return cls._create_filetransfer_task( source_node=source_node, target_node=target_node, source_path_list=source_path_list, target_path=target_path, created_by=created_by, default_mode=default_mode ) # 实际创建任务 # 可能的情况 # 1.source_node 是当前节点,target_node 是其他节点, 此时为上传 # 2.target_node 是当前节点, 此时为下载 @classmethod def _create_filetransfer_task(cls, source_node: dict, target_node: dict, source_path_list: List[dict], target_path: str, created_by: str, default_mode: str = "cover") -> Dict: if source_node["name"] == "local": task_action = "upload" check_node = LocalNode() if target_node["lpver"]: t_node = LPanelNode(target_node["address"], target_node["api_key"], target_node["lpver"]) err = t_node.test_conn() else: t_node = ServerNode(target_node["address"], target_node["api_key"], target_node["app_key"]) err = t_node.test_conn() # public.print_log(target_node["address"], err) if err: return public.return_message(-1, 0,"{} Node cannot connect, error message: {}".format(target_node["name"], err)) elif target_node["name"] == "local": task_action = "download" if source_node["lpver"]: check_node = LPanelNode(source_node["address"], source_node["api_key"], source_node["lpver"]) err = check_node.test_conn() else: check_node = ServerNode(source_node["address"], source_node["api_key"], source_node["app_key"]) err = check_node.test_conn() # public.print_log(source_node["address"], err) if err: return public.return_message(-1, 0,"{} Node cannot connect, error message: {}".format(source_node["name"], err)) else: return public.return_message(-1, 0,"Node information that cannot be processed") if check_node.__class__ is ServerNode: ver_check = check_node.filetransfer_version_check() if ver_check: return public.return_message(-1, 0,"{} Node check error::".format(source_node["name"]) + ver_check) target_path = target_path.rstrip("/") file_list = [] for src_item in source_path_list: if src_item["is_dir"]: f_list, err = check_node.dir_walk(src_item["path"]) if err: return public.return_message(-1, 0,err) if not f_list: src_item["dst_file"] = os.path.join(target_path, os.path.basename(src_item["path"])) file_list.append(src_item) else: for f_item in f_list: f_item["dst_file"] = f_item["path"].replace(os.path.dirname(src_item["path"]), target_path) file_list.append(f_item) else: src_item["dst_file"] = os.path.join(target_path, os.path.basename(src_item["path"])) file_list.append(src_item) if len(file_list) > 1000: return public.return_message(-1, 0,"More than 1000 files, please compress before transferring") db = FileTransferDB() tt = FileTransferTask( source_node=source_node, target_node=target_node, source_path_list=source_path_list, target_path=target_path, task_action=task_action, status="pending", created_by=created_by, default_mode=default_mode, is_source_node=source_node["name"] == "local", is_target_node=target_node["name"] == "local", ) err = db.create_task(tt) if err: return public.return_message(-1, 0,err) ft_list = [] for f_item in file_list: ft = FileTransfer( task_id=tt.task_id, src_file=f_item["path"], dst_file=f_item["dst_file"], file_size=f_item["size"], is_dir=f_item.get("is_dir", 0), status="pending", progress=0, ) ft_list.append(ft) if not ft_list: return public.return_message(-1, 0,"There are no files available for transfer") err = db.batch_create_file_transfers(ft_list) if err: db.delete_task(tt.task_id) return public.return_message(-1, 0,err) py_bin = public.get_python_bin() log_file = "{}/task_{}.log".format(cls.log_dir, tt.task_id) start_task = "nohup {} {}/script/node_file_transfers.py {} > {} 2>&1 &".format( py_bin, public.get_panel_path(), tt.task_id, log_file, ) res = public.ExecShell(start_task) wait_timeout = wait_running(tt.task_id, timeout=10.0) if wait_timeout: return public.return_message(-1, 0,wait_timeout) return public.return_message(0, 0,tt.to_dict()) @staticmethod def file_list(get): node_id = get.get("node_id/d", -1) p = get.get("p/d", 1) row = get.get("showRow/d", 50) path = get.get("path/s", "") search = get.get("search/s", "") if node_id == -1: return public.return_message(-1, 0,"Node parameter error") if node_id == 0: node = LocalNode() else: node = ServerNode.new_by_id(node_id) if not node: return public.return_message(-1, 0,"Node does not exist") if not path: return public.return_message(-1, 0,"Path parameter error") data, err = node.file_list(path, p, row, search) if err: return public.return_message(-1, 0,err) if "status" not in data and "message" not in data:return public.return_message(0, 0,data) return data @staticmethod def delete_file(get): node_id = get.get("node_id/d", -1) if node_id == -1: return public.return_message(-1, 0,"Node parameter error") if node_id == 0: node = LocalNode() else: node = ServerNode.new_by_id(node_id) if not node: return public.return_message(-1, 0,"Node does not exist") path = get.get("path/s", "") is_dir = get.get("is_dir/d", 0) if not path: return public.return_message(-1, 0,"Path parameter error") res=node.remove_file(path, is_dir=is_dir == 1) if res.get('status',-1)==0: return public.return_message(0, 0,res.get('message',{}).get('result',"File/Directory deleted successfully or moved to recycle bin!")) return public.return_message(-1, 0,res.get('msg',"File delete failed")) # return node.remove_file(path, is_dir=is_dir == 1) def transfer_status(self, get): ws: simple_websocket.Server = getattr(get, '_ws', None) if not ws: return jpublic.return_message(-1, 0,"Please use WebSocket connection") ft_db = FileTransferDB() task_data, err = ft_db.get_last_task() if err: ws.send(json.dumps({"type": "error", "msg": err})) return if not task_data: ws.send(json.dumps({"type": "end", "msg": "No tasks"})) return task = FileTransferTask.from_dict(task_data) if task.target_task_id: if task.task_action == "upload": run_node_id = task.source_node["node_id"] else: run_node_id = task.target_node["node_id"] run_node = ServerNode.new_by_id(run_node_id) res = run_node.get_transfer_status(task.target_task_id) if not res["status"]: ws.send(json.dumps({"type": "error", "msg": res["msg"]})) return if res["data"]["task"]["status"] in ("complete", "failed"): task.status = res["data"]["task"]["status"] task.completed_at = res["data"]["task"]["completed_at"] ft_db.update_task(task) res_data = res["data"] res_data["type"] = "end" res_data["msg"] = "Mission completed" ws.send(json.dumps(res_data)) return run_node.proxy_transfer_status(task.target_task_id, ws) else: if task.status in ("complete", "failed"): data, _ = ft_db.last_task_all_status() data.update({"type": "end", "msg": "Mission completed"}) ws.send(json.dumps(data)) return self._proxy_transfer_status(task, ws) def node_proxy_transfer_status(self, get): ws: simple_websocket.Server = getattr(get, '_ws', None) if not ws: return public.return_message(-1, 0, "Please use WebSocket connection") task_id = get.get("task_id/d", 0) if not task_id: ws.send(json.dumps({"type": "error", "msg": "Task ID parameter error"})) return ft_db = FileTransferDB() task_data, err = ft_db.get_task(task_id) if err: ws.send(json.dumps({"type": "error", "msg": err})) return task = FileTransferTask.from_dict(task_data) if task.status in ("complete", "failed"): data, _ = ft_db.last_task_all_status() data["type"] = "end" data["msg"] = "Mission completed" ws.send(json.dumps(data)) return self._proxy_transfer_status(task, ws) @staticmethod def _proxy_transfer_status(task: FileTransferTask, ws: simple_websocket.Server): def call_log(data): if isinstance(data, str): ws.send(json.dumps({"type": "end", "msg": data})) else: if data["task"]["status"] in ("complete", "failed"): data["msg"] = "Mission completed" data["type"] = "end" else: data["type"] = "status" data["msg"] = "Task in progress" ws.send(json.dumps(data)) task_running_log(task.task_id, call_log) @staticmethod def get_transfer_status(get): task_id = get.get("task_id/d", 0) if not task_id: return public.return_message(-1, 0, "Task ID parameter error") ft_db = FileTransferDB() task_data, err = ft_db.get_task(task_id) if err: return public.return_message(-1, 0, err) task = FileTransferTask.from_dict(task_data) file_list = ft_db.get_task_file_transfers(task_id) return public.return_message(0, 0, { "task": task.to_dict(), "file_list": file_list, }) @staticmethod def upload_check(get): node_id = get.get("node_id/d", -1) if node_id == -1: return public.return_message(-1, 0,"Node parameter error") if node_id == 0: node = LocalNode() else: node = ServerNode.new_by_id(node_id) if not node: return public.return_message(-1, 0,"Node does not exist") filename = get.get("files/s", "") if "\n" in filename: f_list = filename.split("\n") else: f_list = [filename] res, err = node.upload_check(f_list) if err: return public.return_message(-1, 0,err) if 'message' not in res and 'status' not in res:return public.return_message(0, 0,res) return res @staticmethod def dir_size(get): node_id = get.get("node_id/d", -1) if node_id < 0: return public.return_message(-1, 0,"Node parameter error") if node_id == 0: node = LocalNode() else: node = ServerNode.new_by_id(node_id) if not node: return public.return_message(-1, 0,"Node does not exist") path = get.get("path/s", "") size, err = node.dir_size(path) if err: return public.return_message(-1, 0,err) return public.return_message(0, 0, { "size": public.to_size(size), "size_b": size, }) @staticmethod def create_dir(get): node_id = get.get("node_id/d", -1) if node_id < 0: return public.return_message(-1, 0,"Node parameter error") if node_id == 0: node = LocalNode() else: node = ServerNode.new_by_id(node_id) if not node: return public.return_message(-1, 0,"Node does not exist") path = get.get("path/s", "") res, err = node.create_dir(path) if err: return public.return_message(-1, 0,err) # if res["status"]: # return public.return_message(0, 0,res["msg"]) return public.return_message(0, 0,"Successfully created directory") # return res @staticmethod def delete_dir(get): node_id = get.get("node_id/d", -1) if node_id < 0: return public.return_message(-1, 0,"Node parameter error") if res["status"]: return public.return_message(0, 0,res["msg"]) return public.return_message(-1, 0,res["msg"]) @staticmethod def node_get_dir(get): node_id = get.get("node_id/d", -1) if node_id < 0: return public.return_message(-1, 0,"Node parameter error") if node_id == 0: node = LocalNode() else: node = ServerNode.new_by_id(node_id) if not node: return public.return_message(-1, 0,"Node does not exist") search = get.get("search/s", "") disk = get.get("disk/s", "") path = get.get("path/s", "") return node.get_dir(path, search, disk)