Files
computer-craft-web-file/Client/main.lua

471 lines
14 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 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
-- 改进的二进制检测函数增加对DFPWM等特定格式的支持
local function isBinaryFile(path)
local extension = string.lower(string.match(path, "%.([^%.%s]+)$") or "")
local binaryExtensions = {
["wav"] = true,
["mp3"] = true,
["ogg"] = true,
["flac"] = true,
["dfpwm"] = true,
["png"] = true,
["jpg"] = true,
["jpeg"] = true,
["gif"] = true,
["bmp"] = true,
["ico"] = true,
["exe"] = true,
["dll"] = true,
["so"] = true,
["bin"] = true,
["dat"] = true,
["zip"] = true,
["rar"] = true,
["tar"] = true,
["gz"] = true,
["pdf"] = true,
["doc"] = true,
["docx"] = true,
["xls"] = true,
["xlsx"] = true,
["ppt"] = true,
["pptx"] = true
}
if binaryExtensions[extension] then
return true
end
-- 对于没有扩展名的文件,检查内容
local absPath = path
if not fs.exists(absPath) then
return false
end
local ok, handle = pcall(fs.open, absPath, "rb")
if not ok or not handle then
return false
end
local data = handle.read(math.min(1024, fs.getSize(absPath)))
handle.close()
if not data then
return false
end
-- 检查是否存在控制字符(除常见的空白字符外)
for i = 1, #data do
local b = data:byte(i)
-- 控制字符范围是 0-8, 11-12, 14-31, 127
if (b >= 0 and b <= 8) or (b == 11) or (b == 12) or (b >= 14 and b <= 31) or (b == 127) then
-- 如果控制字符过多超过5%),则认为是二进制文件
local controlCount = 0
for j = 1, #data do
local byte = data:byte(j)
if (byte >= 0 and byte <= 8) or (byte == 11) or (byte == 12) or (byte >= 14 and byte <= 31) or (byte == 127) then
controlCount = controlCount + 1
end
end
if controlCount / #data > 0.05 then
return true
end
end
end
return false
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 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]"
if not isBinaryFile(absPath) then
local ok, handle = pcall(fs.open, absPath, "r")
if ok and handle then
local data = handle.readAll()
handle.close()
content = data or ""
end
end
result[fullPrefix] = { isFile = true, content = content, isBinary = isBinaryFile(absPath) }
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)