· 6 years ago · Mar 17, 2020, 10:22 PM
1--execute: pastebin get S8w2erYx /boot/98_serverfilesystem.lua
2--creds to: https://oc.cil.li/topic/844-serverfs-host-a-filesystem-over-the-network/
3------------------------------
4-- Server Filesystem Script --
5------------------------------
6-- Configuration --
7local timeoutPeriod = 10
8local useTunnelOverModem = true
9local autoMountServerFS = true
10local activateServerFSModule = true
11local virtualizeServerFSComponent = true
12local throwSoftErrors = false
13
14-- Modules
15local computer = require("computer")
16local component = require("component")
17local serialization = require("serialization")
18local event = require("event")
19local filesystem = require("filesystem")
20
21-- Set up the server filesystem component --
22local serverFS = {
23 type = "filesystem",
24 address = "0000-remoteFS"
25}
26local functionList = {
27 "spaceTotal", "spaceUsed", "spaceFree", "getLabel", "setLabel", "isReadOnly",
28 "exists", "isDirectory", "size", "lastModified", "list",
29 "rename", "remove", "makeDirectory",
30 "open", "close", "read", "write", "seek",
31}
32local functionHelp = {
33 spaceUsed = "function():number -- The currently used capacity of the file system, in bytes.",
34 spaceFree = "function():number -- The current amount of free space on the file system, in bytes.",
35 open = "function(path:string[, mode:string='r']):number -- Opens a new file descriptor and returns its handle.",
36 seek = "function(handle:number, whence:string, offset:number):number -- Seeks in an open file descriptor with the specified handle. Returns the new pointer position.",
37 makeDirectory = "function(path:string):boolean -- Creates a directory at the specified absolute path in the file system. Creates parent directories, if necessary.",
38 exists = "function(path:string):boolean -- Returns whether an object exists at the specified absolute path in the file system.",
39 isReadOnly = "function():boolean -- Returns whether the file system is read-only.",
40 write = "function(handle:number, value:string):boolean -- Writes the specified data to an open file descriptor with the specified handle.",
41 spaceTotal = "function():number -- The overall capacity of the file system, in bytes.",
42 isDirectory = "function(path:string):boolean -- Returns whether the object at the specified absolute path in the file system is a directory.",
43 rename = "function(from:string, to:string):boolean -- Renames/moves an object from the first specified absolute path in the file system to the second.",
44 list = "function(path:string):table -- Returns a list of names of objects in the directory at the specified absolute path in the file system.",
45 lastModified = "function(path:string):number -- Returns the (real world) timestamp of when the object at the specified absolute path in the file system was modified.",
46 getLabel = "function():string -- Get the current label of the file system.",
47 remove = "function(path:string):boolean -- Removes the object at the specified absolute path in the file system.",
48 close = "function(handle:number) -- Closes an open file descriptor with the specified handle.",
49 size = "function(path:string):number -- Returns the size of the object at the specified absolute path in the file system.",
50 read = "function(handle:number, count:number):string or nil -- Reads up to the specified amount of data from an open file descriptor with the specified handle. Returns nil when EOF is reached.",
51 setLabel = "function(value:string):string -- Sets the label of the file system. Returns the new value, which may be truncated.",
52}
53
54-- Build the serverFS component module --
55local lastCall = computer.uptime()
56for key, value in ipairs(functionList) do
57 serverFS[value] = setmetatable({}, {
58 __call = function(self, ...)
59 if computer.uptime() - lastCall < .7 then
60 os.sleep(.7 - (computer.uptime() - lastCall))
61 end
62 lastCall = computer.uptime()
63 local foundModem, modem = pcall(component.getPrimary, "modem")
64 local foundTunnel, tunnel = pcall(component.getPrimary, "tunnel")
65
66 -- If the function errored then the modem has not been found and is just an error message.
67 if not foundModem then modem = nil end
68 if not foundTunnel then tunnel = nil end
69
70 -- Try to find a tunnel or modem to use if no primary component is set.
71 if not (modem or tunnel) then
72 for address, componentType in component.list() do
73 if componentType == "modem" and not modem then
74 modem = component.proxy(address)
75 elseif componentType == "tunnel" and not tunnel then
76 tunnel = component.proxy(address)
77 end
78 end
79 end
80
81 -- Determine if we want to use the modem or tunnel.
82 if (tunnel and modem and useTunnelOverModem) or (tunnel and (not modem)) then
83 -- Use the tunnel
84 tunnel.send(serialization.serialize({value, ...}))
85 while true do
86 local messageRecieved, address, from, port, _, message = event.pull(timeoutPeriod, "modem_message")
87 if not messageRecieved then
88 return false, "The request timed out. The server may not be online or is handling too many requests."
89 end
90 if address == tunnel.address then
91 local args = serialization.unserialize(message)
92 if args[1] == true then
93 table.remove(args, 1)
94 else
95 if throwSoftErrors then
96 return false, args[2]
97 else
98 error(args[1], 2)
99 end
100 end
101 for key, value in pairs(args) do
102 if tonumber(value) then
103 args[key] = tonumber(value)
104 elseif value == "true" then
105 args[key] = true
106 elseif value == "false" then
107 args[key] = false
108 end
109 end
110 return table.unpack(args)
111 end
112 end
113 elseif (modem) then
114 -- Use the modem
115 local isOpenBeforeThisRequest = modem.isOpen(280)
116 modem.open(280)
117 modem.broadcast(300, serialization.serialize({value, ...}))
118 while true do
119 local messageRecieved, _, from, port, _, message = event.pull(timeoutPeriod, "modem_message")
120 if not messageRecieved then
121 return false, "The request timed out. The server may not be online or is handling too many requests."
122 end
123 if port == 280 then
124 local args = serialization.unserialize(message)
125 if args[1] == true then
126 table.remove(args, 1)
127 else
128 if throwSoftErrors then
129 return false, args[2]
130 else
131 error(args[1], 2)
132 end
133 end
134 for key, value in pairs(args) do
135 if tonumber(value) then
136 args[key] = tonumber(value)
137 elseif value == "true" then
138 args[key] = true
139 elseif value == "false" then
140 args[key] = false
141 end
142 end
143 return table.unpack(args)
144 end
145 end
146 if not isOpenBeforeThisRequest then -- Close it again if it was closed before we called this request
147 modem.close(280)
148 end
149 else
150 -- No primary tunnel or modem found.
151 if throwSoftErrors then
152 return false, "No tunnel or modem found. Please insert a tunnel or modem component, then try again."
153 else
154 error("No tunnel or modem found. Please insert a tunnel or modem component, then try again.", 2)
155 end
156 end
157 end,
158 __tostring = functionHelp[value] or "function()",
159 })
160end
161
162-- Mount this filesystem --
163if autoMountServerFS then
164 local success, errorMessage = filesystem.mount(serverFS, "srv")
165 if not success then
166 io.stderr:write("Failed to mount server: "..tostring(errorMessage).."\n")
167 end
168end
169
170-- Add this to the loaded modules --
171if activateServerFSModule then
172 package.loaded["serverFS"] = serverFS
173end
174
175-- Insert the virtual serverFS component to the component API --
176if virtualizeServerFSComponent then
177 pcall(function()
178 local component = require("component")
179 local serverFS = serverFS
180 if not serverFS then
181 serverFS = require("serverFS")
182 end
183
184 local oldDoc = component.doc
185 local oldList = component.list
186 local oldProxy = component.proxy
187 local oldInvoke = component.invoke
188 local oldMethods = component.methods
189 local oldType = component.type
190
191 local sfsAddress = serverFS.address
192
193 function component.doc(address, method)
194 if address == sfsAddress then
195 return tostring(serverFS[method])
196 end
197 return oldDoc
198 end
199 function component.list(filter, exact)
200 if type(filter) == "string" then
201 if (exact and filter == "filesystem") or (not exact and filter:find(("filesystem"):sub(1, #filter))) then
202 local realItems = {}
203 for x, y in oldList(filter, exact or false) do
204 realItems[x] = y
205 end
206 realItems[sfsAddress] = serverFS.type
207 local ilterator = function()
208 for x, y in pairs(realItems) do
209 coroutine.yield(x, y)
210 end
211 end
212 return coroutine.wrap(ilterator)
213 end
214 return oldList(filter, exact)
215 end
216 return oldList(filter, exact)
217 end
218 function component.proxy(address)
219 if address == sfsAddress then
220 return serverFS
221 end
222 return oldProxy(address)
223 end
224 function component.invoke(address, funct, ...)
225 if address == sfsAddress then
226 return serverFS[funct](...)
227 end
228 return oldInvoke(address, funct, ...)
229 end
230 function component.methods(address)
231 if address == sfsAddress then
232 local functions = {}
233 for key, value in pairs(serverFS) do
234 if type(value) == "function" then
235 functions[key] = false
236 end
237 end
238 return functions
239 end
240 return oldMethods(address)
241 end
242 function component.type(address)
243 if address == sfsAddress then
244 return "filesystem"
245 end
246 return oldType(address)
247 end
248 end)
249end