-- simple_file_client.lua local args = {...} local httpServer = args[1] or "http://192.168.2.200:8080" local roomId = args[2] local pollInterval = 1 local computerID = tostring(os.computerID() or "unknown") -- ========== 加载 Basalt ========== local basaltUrl = "https://git.liulikeji.cn/GitHub/Basalt/releases/download/v1.7/basalt.lua" local basaltResp = http.get(basaltUrl) if not basaltResp then error("无法下载 Basalt 框架") end local basalt = load(basaltResp.readAll())() basaltResp.close() local mainFrame = basalt.createFrame() -- ========== 工具函数 ========== local function log(msg) --basalt.debug("[FileClient] " .. tostring(msg)) end function table_to_json(t, indent) indent = indent or 0 local spaces = string.rep(" ", indent) local result = {} if type(t) ~= "table" then if type(t) == "string" then -- 正确转义所有特殊字符 local escaped = t:gsub("[\\\"\b\f\n\r\t]", function(c) local replacements = { ['\\'] = '\\\\', ['"'] = '\\"', ['\b'] = '\\b', ['\f'] = '\\f', ['\n'] = '\\n', ['\r'] = '\\r', ['\t'] = '\\t' } return replacements[c] end) return '"' .. escaped .. '"' elseif type(t) == "number" or type(t) == "boolean" then return tostring(t) else return '"' .. tostring(t) .. '"' end end -- 检查是否是数组 local is_array = true local max_index = 0 local count = 0 for k, v in pairs(t) do count = count + 1 if type(k) ~= "number" or k <= 0 or math.floor(k) ~= k then is_array = false end if type(k) == "number" and k > max_index then max_index = k end end -- 空表当作对象处理 if count == 0 then is_array = false end if is_array then -- 处理数组 table.insert(result, "[") local items = {} for i = 1, max_index do if t[i] ~= nil then table.insert(items, table_to_json(t[i], indent + 2)) else table.insert(items, "null") end end table.insert(result, table.concat(items, ", ")) table.insert(result, "]") else -- 处理对象 table.insert(result, "{") local items = {} for k, v in pairs(t) do local key = '"' .. tostring(k) .. '"' local value = table_to_json(v, indent + 2) if indent > 0 then table.insert(items, spaces .. " " .. key .. ": " .. value) else table.insert(items, key .. ":" .. value) end end if indent > 0 then table.insert(result, table.concat(items, ",\n")) table.insert(result, "\n" .. spaces .. "}") else table.insert(result, table.concat(items, ",")) table.insert(result, "}") end end return table.concat(result, indent > 0 and "\n" .. spaces or "") end local function cleanPath(path) return (path:gsub("^computer/", ""):gsub("^computer\\", "")) end local function httpPost(path, data) local jsonData = table_to_json(data) local url = httpServer .. path -- 使用长轮询 local response,err = http.post({ url = url, body = jsonData, method = "POST", headers = { ["Content-Type"] = "application/json" }, timeout = 60 }) if not response then return nil, "0 "..err end local responseBody = response.readAll() response.close() local ok, result = pcall(textutils.unserialiseJSON, responseBody) if ok then return result else return nil, "1无效的JSON响应: " .. responseBody end end -- ========== 文件系统操作 ========== local function isLikelyText(data) for i = 1, math.min(#data, 1024) do local b = data:byte(i) if b < 32 and b ~= 9 and b ~= 10 and b ~= 13 then return false end end return true end local function getFiles(currentPath, result, prefix) local computerPrefix = "computer_" .. computerID local fullPrefix = currentPath == "" and prefix:sub(1, -2) or prefix .. currentPath local absPath = "/" .. (currentPath == "" and "" or currentPath) if fs.isDir(absPath) then result[fullPrefix] = { isFolder = true } for _, entry in ipairs(fs.list(absPath)) do if entry ~= "rom" then getFiles(currentPath == "" and entry or (currentPath .. "/" .. entry), result, prefix) end end else local content = "[binary]" local ok, handle = pcall(fs.open, absPath, "rb") if ok and handle then local data = handle.readAll() handle.close() if data and isLikelyText(data) then content = data end end result[fullPrefix] = { isFile = true, content = content } end end local function fetchFiles() local files = {} getFiles("", files, "computer_" .. computerID .. "/") return files end local function saveFile(path, content) path = cleanPath(path):gsub("^computer[" .. computerID .. "_]*/", "") local dir = fs.getDir(path) if not fs.exists(dir) then fs.makeDir(dir) end local f = fs.open(path, "w") f.write(content or "") f.close() end local function createFile(path) path = cleanPath(path):gsub("^computer[" .. computerID .. "_]*/", "") local dir = fs.getDir(path) if not fs.exists(dir) then fs.makeDir(dir) end if not fs.exists(path) then local f = fs.open(path, "w") f.close() end end local function createFolder(path) path = cleanPath(path):gsub("^computer[" .. computerID .. "_]*/", "") fs.makeDir(path) end local function renameFile(oldPath, newPath) oldPath = cleanPath(oldPath):gsub("^computer[" .. computerID .. "_]*/", "") newPath = cleanPath(newPath):gsub("^computer[" .. computerID .. "_]*/", "") fs.move(oldPath, newPath) end local function deleteFile(path) path = cleanPath(path):gsub("^computer[" .. computerID .. "_]*/", "") if fs.exists(path) then fs.delete(path) end end -- ========== 消息处理函数 ========== local function handleFetchFiles(reqId, sender) local success, result = pcall(fetchFiles) return { type = "file_operation_response", requestId = reqId, success = success, data = success and result or nil, error = success and nil or tostring(result), target_client_id = sender } end local function handleSaveFile(data, reqId, sender) local success, err = pcall(saveFile, data.path, data.content) return { type = "file_operation_response", requestId = reqId, success = success, error = success and nil or tostring(err), target_client_id = sender } end local function handleCreateFile(data, reqId, sender) local success, err = pcall(createFile, data.path) return { type = "file_operation_response", requestId = reqId, success = success, error = success and nil or tostring(err), target_client_id = sender } end local function handleCreateFolder(data, reqId, sender) local success, err = pcall(createFolder, data.path) return { type = "file_operation_response", requestId = reqId, success = success, error = success and nil or tostring(err), target_client_id = sender } end local function handleRename(data, reqId, sender) local success, err = pcall(renameFile, data.path, data.newPath) return { type = "file_operation_response", requestId = reqId, success = success, error = success and nil or tostring(err), target_client_id = sender } end local function handleDelete(data, reqId, sender) local success, err = pcall(deleteFile, data.path) return { type = "file_operation_response", requestId = reqId, success = success, error = success and nil or tostring(err), target_client_id = sender } end -- ========== 房间管理函数 ========== local function createRoom() local result, err = httpPost("/api/room", {}) if not result then error("无法创建房间: " .. tostring(err)) end return result.room_id end local function sendResponse(response) if response and roomId then local result, err = httpPost("/api/client/send", { room_id = roomId, message = response }) if not result then log("3 发送响应失败: " .. tostring(err)) end end end local function pollMessages() while true do if not roomId then sleep(pollInterval) break end local result, err = httpPost("/api/client/receive", { room_id = roomId }) if result and result.success then local msg = result.message log(msg) if msg then local msgType = msg.type if msgType == "file_operation" or msgType == "file_operation_request" then local op = msg.operation_type or msg.type local data = msg.data or {} local reqId = msg.requestId or msg.request_id local sender = msg.sender_id local response if op == "fetch_files" then response = handleFetchFiles(reqId, sender) elseif op == "create_or_save_file" then response = handleSaveFile(data, reqId, sender) elseif op == "new_file" then response = handleCreateFile(data, reqId, sender) elseif op == "new_folder" then response = handleCreateFolder(data, reqId, sender) elseif op == "rename" then response = handleRename(data, reqId, sender) elseif op == "delete_file" then response = handleDelete(data, reqId, sender) else response = { type = "file_operation_response", requestId = reqId, success = false, error = "Unknown operation: " .. tostring(op), target_client_id = sender } end sendResponse(response) end end elseif err then log("2 轮询错误: " .. tostring(err)) -- 如果是连接错误,稍后再试 sleep(5) end end end -- ========== 主函数 ========== local function main() if not roomId then roomId = createRoom() log("创建新房间: " .. roomId) else log("使用现有房间: " .. roomId) end mainFrame:addProgram():execute(function() shell.run("shell") end):setSize("parent.w","parent.h") os.queueEvent("mouse_click",1,1,1) -- 启动消息轮询 mainFrame:addThread():start(pollMessages) log("客户端已启动。房间ID: " .. roomId) log("计算机ID: " .. computerID) log("按 Q 退出") -- 主循环 while true do local event, param1 = os.pullEvent() if event == "key" and param1 == keys.q then log("用户按 Q 退出") break end end end -- 启动主逻辑和Basalt事件循环 parallel.waitForAll(basalt.autoUpdate, main)