MineOS/lib/MineOSNetwork.lua

742 lines
19 KiB
Lua
Executable File

local component = require("component")
local computer = require("computer")
local MineOSCore = require("MineOSCore")
local MineOSPaths = require("MineOSPaths")
local GUI = require("GUI")
local event = require("event")
local fs = require("filesystem")
local MineOSNetwork = {}
----------------------------------------------------------------------------------------------------------------
local filesystemProxy = component.proxy(computer.getBootAddress())
MineOSNetwork.filesystemHandles = {}
local modemMaxPacketSize = 8192
local modemPacketReserve = 128
MineOSNetwork.modemProxy = nil
MineOSNetwork.modemPort = 1488
MineOSNetwork.modemTimeout = 2
MineOSNetwork.internetProxy = nil
MineOSNetwork.internetDelay = 0.05
MineOSNetwork.internetTimeout = 0.25
MineOSNetwork.proxySpaceUsed = 0
MineOSNetwork.proxySpaceTotal = 1073741824
MineOSNetwork.mountPaths = {
modem = MineOSPaths.network .. "Modem/",
FTP = MineOSPaths.network .. "FTP/"
}
----------------------------------------------------------------------------------------------------------------
local function umountProxy(type)
for proxy in fs.mounts() do
if proxy[type] then
fs.umount(proxy)
end
end
end
function MineOSNetwork.updateComponents()
local modemAddress, internetAddress = component.list("modem")(), component.list("internet")()
if modemAddress then
MineOSNetwork.modemProxy = component.proxy(modemAddress)
MineOSNetwork.modemProxy.open(MineOSNetwork.modemPort)
else
MineOSNetwork.modemProxy = nil
MineOSNetwork.umountModems()
end
if internetAddress then
MineOSNetwork.internetProxy = component.proxy(internetAddress)
else
MineOSNetwork.internetProxy = nil
MineOSNetwork.umountFTPs()
end
end
----------------------------------------------------------------------------------------------------------------
function MineOSNetwork.umountFTPs()
umountProxy("MineOSNetworkFTP")
end
function MineOSNetwork.getFTPProxyName(address, port, user)
return user .. "@" .. address .. ":" .. port
end
local function FTPSocketWrite(socketHandle, data)
local success, result = pcall(socketHandle.write, data .. "\r\n")
if success then
return true, result
else
return false, result
end
end
local function FTPSocketRead(socketHandle)
os.sleep(MineOSNetwork.internetDelay)
local deadline, data, success, result = computer.uptime() + MineOSNetwork.internetTimeout, ""
while computer.uptime() < deadline do
success, result = pcall(socketHandle.read, math.huge)
if success then
if not result or #result == 0 then
if #data > 0 then
return true, data
end
else
data, deadline = data .. result, computer.uptime() + MineOSNetwork.internetTimeout
end
else
return false, result
end
end
return false, "Socket read time out"
end
local function FTPParseLines(data)
local lines = {}
for line in data:gmatch("[^\r\n]+") do
table.insert(lines, line)
end
return lines
end
local function FTPLogin(socketHandle, user, password)
FTPSocketWrite(socketHandle, "USER " .. user)
FTPSocketWrite(socketHandle, "PASS " .. password)
FTPSocketWrite(socketHandle, "TYPE I")
local success, result = FTPSocketRead(socketHandle)
if success then
if result:match("TYPE okay") then
return true
else
return false, "Authentication failed"
end
else
return false, result
end
end
local function FTPEnterPassiveModeAndRunCommand(commandSocketHandle, command, dataToWrite)
FTPSocketWrite(commandSocketHandle, "PASV")
local success, result = FTPSocketRead(commandSocketHandle)
if success then
local digits = {result:match("Entering Passive Mode %((%d+),(%d+),(%d+),(%d+),(%d+),(%d+)%)")}
if #digits == 6 then
local address, port = table.concat(digits, ".", 1, 4), tonumber(digits[5]) * 256 + tonumber(digits[6])
FTPSocketWrite(commandSocketHandle, command)
local dataSocketHandle = MineOSNetwork.internetProxy.connect(address, port)
if dataToWrite then
os.sleep(MineOSNetwork.internetDelay)
dataSocketHandle.read(1)
dataSocketHandle.write(dataToWrite)
dataSocketHandle.close()
return true
else
local success, result = FTPSocketRead(dataSocketHandle)
dataSocketHandle.close()
if success then
return true, result
else
return false, result
end
end
else
return false, "Entering passive mode failed: wrong address byte array. Socket response message was: " .. tostring(result)
end
else
return false, result
end
end
local function FTPParseFileInfo(result)
local size, year, month, day, hour, minute, sec, type, name = result:match("Size=(%d+);Modify=(%d%d%d%d)(%d%d)(%d%d)(%d%d)(%d%d)(%d%d)[^;]*;Type=([^;]+);%s([^\r\n]+)")
if size then
return
true,
name,
type == "dir",
tonumber(size),
os.time({
year = year,
day = day,
month = month,
hour = hour,
minute = minute,
sec = sec
})
else
return false, "File not exists"
end
end
local function FTPFileInfo(socketHandle, path, field)
FTPSocketWrite(socketHandle, "MLST " .. path)
local success, result = FTPSocketRead(socketHandle)
if success then
local success, name, isDirectory, size, lastModified = FTPParseFileInfo(result)
if success then
if field == "isDirectory" then
return true, isDirectory
elseif field == "lastModified" then
return true, lastModified
else
return true, size
end
else
return true, false
end
else
return false, result
end
end
local function check(...)
local result = {...}
if not result[1] then
GUI.error(table.unpack(result, 2))
end
return table.unpack(result)
end
function MineOSNetwork.connectToFTP(address, port, user, password)
if MineOSNetwork.internetProxy then
local socketHandle, reason = MineOSNetwork.internetProxy.connect(address, port)
if socketHandle then
FTPSocketRead(socketHandle)
local result, reason = FTPLogin(socketHandle, user, password)
if result then
local proxy, fileHandles, label = {}, {}, MineOSNetwork.getFTPProxyName(address, port, user)
proxy.type = "filesystem"
proxy.slot = 0
proxy.address = label
proxy.MineOSNetworkFTP = true
proxy.getLabel = function()
return label
end
proxy.spaceUsed = function()
return MineOSNetwork.proxySpaceUsed
end
proxy.spaceTotal = function()
return MineOSNetwork.proxySpaceTotal
end
proxy.setLabel = function(text)
label = text
return true
end
proxy.isReadOnly = function()
return false
end
proxy.closeSocketHandle = function()
fs.umount(proxy)
return socketHandle.close()
end
proxy.list = function(path)
local success, result = FTPEnterPassiveModeAndRunCommand(socketHandle, "MLSD -a " .. path)
if success then
local list = FTPParseLines(result)
for i = 1, #list do
local success, name, isDirectory = FTPParseFileInfo(list[i])
if success then
list[i] = name .. (isDirectory and "/" or "")
end
end
return list
else
return {}
end
end
proxy.isDirectory = function(path)
local success, result = check(FTPFileInfo(socketHandle, path, "isDirectory"))
if success then
return result
else
return false
end
end
proxy.lastModified = function(path)
local success, result = check(FTPFileInfo(socketHandle, path, "lastModified"))
if success then
return result
else
return 0
end
end
proxy.size = function(path)
local success, result = check(FTPFileInfo(socketHandle, path, "size"))
if success then
return result
else
return 0
end
end
proxy.exists = function(path)
local success, result = check(FTPFileInfo(socketHandle, path))
if success then
return result
else
return false
end
end
proxy.open = function(path, mode)
local temporaryPath = MineOSCore.getTemporaryPath()
if mode == "r" or mode == "rb" or mode == "a" or mode == "ab" then
local success, result = FTPEnterPassiveModeAndRunCommand(socketHandle, "RETR " .. path)
local fileHandle = io.open(temporaryPath, "wb")
fileHandle:write(success and result or "")
fileHandle:close()
end
local fileHandle, reason = filesystemProxy.open(temporaryPath, mode)
if fileHandle then
fileHandles[fileHandle] = {
temporaryPath = temporaryPath,
path = path,
needUpload = mode ~= "r" and mode ~= "rb",
}
end
return fileHandle, reason
end
proxy.close = function(fileHandle)
filesystemProxy.close(fileHandle)
if fileHandles[fileHandle].needUpload then
local file = io.open(fileHandles[fileHandle].temporaryPath, "rb")
local data = file:read("*a")
file:close()
check(FTPEnterPassiveModeAndRunCommand(socketHandle, "STOR " .. fileHandles[fileHandle].path, data))
end
fs.remove(fileHandles[fileHandle].temporaryPath)
fileHandles[fileHandle] = nil
end
proxy.write = function(...)
return filesystemProxy.write(...)
end
proxy.read = function(...)
return filesystemProxy.read(...)
end
proxy.seek = function(...)
return filesystemProxy.seek(...)
end
proxy.remove = function(path)
if proxy.isDirectory(path) then
local list = proxy.list(path)
for i = 1, #list do
proxy.remove((path .. "/" .. list[i]):gsub("/+", "/"))
end
FTPSocketWrite(socketHandle, "RMD " .. path)
else
FTPSocketWrite(socketHandle, "DELE " .. path)
end
end
proxy.makeDirectory = function(path)
FTPSocketWrite(socketHandle, "MKD " .. path)
return true
end
proxy.rename = function(oldPath, newPath)
FTPSocketWrite(socketHandle, "RNFR " .. oldPath)
FTPSocketWrite(socketHandle, "RNTO " .. newPath)
return true
end
return proxy
else
return false, reason
end
else
return false, reason
end
else
return false, "Internet component is not available"
end
end
----------------------------------------------------------------------------------------------------------------
function MineOSNetwork.umountModems()
umountProxy("MineOSNetworkModem")
end
function MineOSNetwork.getModemProxyName(proxy)
return proxy.name and proxy.name .. " (" .. proxy.address .. ")" or proxy.address
end
function MineOSNetwork.getMountedModemProxy(address)
for proxy, path in fs.mounts() do
if proxy.MineOSNetworkModem and proxy.address == address then
return proxy
end
end
end
function MineOSNetwork.sendMessage(address, ...)
if MineOSNetwork.modemProxy then
return MineOSNetwork.modemProxy.send(address, MineOSNetwork.modemPort, ...)
else
MineOSNetwork.modemProxy = nil
return false, "Modem component is not available"
end
end
function MineOSNetwork.broadcastMessage(...)
if MineOSNetwork.modemProxy then
return MineOSNetwork.modemProxy.broadcast(MineOSNetwork.modemPort, ...)
else
MineOSNetwork.modemProxy = nil
return false, "Modem component is not available"
end
end
function MineOSNetwork.setSignalStrength(strength)
if MineOSNetwork.modemProxy then
if MineOSNetwork.modemProxy.isWireless() then
return MineOSNetwork.modemProxy.setStrength(strength)
else
return false, "Modem component is not wireless"
end
else
MineOSNetwork.modemProxy = nil
return false, "Modem component is not available"
end
end
function MineOSNetwork.broadcastComputerState(state)
return MineOSNetwork.broadcastMessage("MineOSNetwork", state and "computerAvailable" or "computerNotAvailable", MineOSCore.properties.network.name)
end
local function newModemProxy(address)
local function request(method, returnOnFailure, ...)
MineOSNetwork.sendMessage(address, "MineOSNetwork", "request", method, ...)
while true do
local eventData = { event.pull(MineOSNetwork.modemTimeout, "modem_message") }
if eventData[3] == address and eventData[6] == "MineOSNetwork" then
if eventData[7] == "response" and eventData[8] == method then
return table.unpack(eventData, 9)
elseif eventData[7] == "accessDenied" then
computer.pushSignal("MineOSNetwork", "accessDenied", address)
return returnOnFailure, "Access denied"
end
elseif not eventData[1] then
local proxy = MineOSNetwork.getMountedModemProxy(address)
if proxy then
fs.umount(proxy)
end
computer.pushSignal("MineOSNetwork", "timeout")
return returnOnFailure, "Network filesystem timeout"
end
end
end
local proxy = {}
proxy.type = "filesystem"
proxy.address = address
proxy.slot = 0
proxy.MineOSNetworkModem = true
proxy.getLabel = function()
return request("getLabel", "N/A")
end
proxy.isReadOnly = function()
return request("isReadOnly", 0)
end
proxy.spaceUsed = function()
return request("spaceUsed", MineOSNetwork.proxySpaceUsed)
end
proxy.spaceTotal = function()
return request("spaceTotal", MineOSNetwork.proxySpaceTotal)
end
proxy.exists = function(path)
return request("exists", false, path)
end
proxy.isDirectory = function(path)
return request("isDirectory", false, path)
end
proxy.makeDirectory = function(path)
return request("makeDirectory", false, path)
end
proxy.setLabel = function(name)
return request("setLabel", false, name)
end
proxy.remove = function(path)
return request("remove", false, path)
end
proxy.lastModified = function(path)
return request("lastModified", 0, path)
end
proxy.size = function(path)
return request("size", 0, path)
end
proxy.list = function(path)
return table.fromString(request("list", "{}", path))
end
proxy.open = function(path, mode)
return request("open", false, path, mode)
end
proxy.close = function(handle)
return request("close", false, handle)
end
proxy.seek = function(...)
return request("seek", 0, ...)
end
proxy.read = function(...)
return request("read", "", ...)
end
proxy.write = function(handle, data)
local maxPacketSize = (MineOSNetwork.modemProxy.maxPacketSize and MineOSNetwork.modemProxy.maxPacketSize() or modemMaxPacketSize) - modemPacketReserve
repeat
if not request("write", false, handle, data:sub(1, maxPacketSize)) then
return false
end
data = data:sub(maxPacketSize + 1)
until #data == 0
return true
end
proxy.rename = function(from, to)
local proxyFrom = fs.get(from)
local proxyTo = fs.get(to)
if proxyFrom.MineOSNetworkModem or proxyTo.MineOSNetworkModem then
local success, handleFrom, handleTo, data, reason = true
handleFrom, reason = proxyFrom.open(from, "rb")
if handleFrom then
handleTo, reason = proxyTo.open(to, "wb")
if handleTo then
while true do
data, readReason = proxyFrom.read(handleFrom, 1024)
if data then
success, reason = proxyTo.write(handleTo, data)
if not success then
break
end
else
success = false
break
end
end
proxyFrom.close(handleTo)
else
success = false
end
proxyFrom.close(handleFrom)
else
success = false
end
if success then
success, reason = proxyFrom.remove(from)
end
return success, reason
else
return request("rename", false, from, to)
end
end
return proxy
end
local exceptionMethods = {
getLabel = function()
return MineOSCore.properties.network.name or MineOSNetwork.modemProxy.address
end,
list = function(path)
return table.toString(filesystemProxy.list(path))
end,
open = function(path, mode)
local ID
while not ID do
ID = math.random(1, 0x7FFFFFFF)
for handleID in pairs(MineOSNetwork.filesystemHandles) do
if handleID == ID then
ID = nil
end
end
end
MineOSNetwork.filesystemHandles[ID] = filesystemProxy.open(path, mode)
return ID
end,
close = function(ID)
local data, reason = filesystemProxy.close(MineOSNetwork.filesystemHandles[ID])
MineOSNetwork.filesystemHandles[ID] = nil
return data, reason
end,
read = function(ID, ...)
return filesystemProxy.read(MineOSNetwork.filesystemHandles[ID], ...)
end,
write = function(ID, ...)
return filesystemProxy.write(MineOSNetwork.filesystemHandles[ID], ...)
end,
seek = function(ID, ...)
return filesystemProxy.seek(MineOSNetwork.filesystemHandles[ID], ...)
end,
}
local function handleRequest(e1, e2, e3, e4, e5, e6, e7, e8, ...)
if MineOSCore.properties.network.users[e3].allowReadAndWrite then
local result = { pcall(exceptionMethods[e8] or filesystemProxy[e8], ...) }
MineOSNetwork.sendMessage(e3, "MineOSNetwork", "response", e8, table.unpack(result, result[1] and 2 or 1))
else
MineOSNetwork.sendMessage(e3, "MineOSNetwork", "accessDenied")
end
end
----------------------------------------------------------------------------------------------------------------
function MineOSNetwork.update()
MineOSNetwork.umountModems()
MineOSNetwork.umountFTPs()
MineOSNetwork.updateComponents()
MineOSNetwork.setSignalStrength(MineOSCore.properties.network.signalStrength)
MineOSNetwork.broadcastComputerState(MineOSCore.properties.network.enabled)
-- if MineOSNetwork.eventHandlerID then
-- event.removeHandler(MineOSNetwork.eventHandlerID)
-- end
-- if MineOSCore.properties.network.enabled then
-- end
end
function MineOSNetwork.disable()
MineOSCore.properties.network.enabled = false
MineOSCore.saveProperties()
MineOSNetwork.update()
end
function MineOSNetwork.enable()
MineOSCore.properties.network.enabled = true
MineOSCore.saveProperties()
MineOSNetwork.update()
end
----------------------------------------------------------------------------------------------------------------
event.register(
nil,
function(e1, e2, e3, e4, e5, e6, e7, e8, ...)
if (e1 == "component_added" or e1 == "component_removed") and (e3 == "modem" or e3 == "internet") then
MineOSNetwork.updateComponents()
MineOSNetwork.broadcastComputerState(MineOSCore.properties.network.enabled)
elseif MineOSCore.properties.network.enabled and e1 == "modem_message" and e6 == "MineOSNetwork" then
if e7 == "request" then
handleRequest(e1, e2, e3, e4, e5, e6, e7, e8, ...)
elseif e7 == "computerAvailable" or e7 == "computerAvailableRedirect" then
for proxy in fs.mounts() do
if proxy.MineOSNetworkModem and proxy.address == e3 then
fs.umount(proxy)
end
end
proxy = newModemProxy(e3)
proxy.name = e8
fs.mount(proxy, MineOSNetwork.mountPaths.modem .. e3 .. "/")
if e7 == "computerAvailable" then
MineOSNetwork.sendMessage(e3, "MineOSNetwork", "computerAvailableRedirect", MineOSCore.properties.network.name)
end
if not MineOSCore.properties.network.users[e3] then
MineOSCore.properties.network.users[e3] = {}
MineOSCore.saveProperties()
end
computer.pushSignal("MineOSNetwork", "updateProxyList")
elseif e7 == "computerNotAvailable" then
local proxy = MineOSNetwork.getMountedModemProxy(e3)
if proxy then
fs.umount(proxy)
end
computer.pushSignal("MineOSNetwork", "updateProxyList")
end
end
end,
math.huge,
math.huge
)
----------------------------------------------------------------------------------------------------------------
return MineOSNetwork