mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-12 16:57:32 -04:00
Merge branch 'master' of cil.li:oc
Conflicts: li/cil/oc/Blocks.scala
This commit is contained in:
commit
4489781e34
@ -64,6 +64,7 @@ flattenAndStore("_ENV", _ENV)
|
||||
--]]
|
||||
-- OK, I admit this is a little crazy... here goes:
|
||||
local function wrap(f)
|
||||
assert(f)
|
||||
-- This is the function that replaces the original API function. It is
|
||||
-- called from userland when it wants something from a driver.
|
||||
return function(...)
|
||||
@ -94,5 +95,5 @@ local function wrap(f)
|
||||
end
|
||||
end
|
||||
|
||||
sendToNode = wrap(sendToNode)
|
||||
sendToAddress = wrap(sendToAddress)
|
||||
nodeName = wrap(nodeName)
|
752
assets/opencomputers/lua/drivers/filesystem.lua
Normal file
752
assets/opencomputers/lua/drivers/filesystem.lua
Normal file
@ -0,0 +1,752 @@
|
||||
local mtab = {children={}}
|
||||
|
||||
local function segments(path)
|
||||
path = path:gsub("\\", "/")
|
||||
repeat local n; path, n = path:gsub("//", "/") until n == 0
|
||||
local parts = {}
|
||||
for part in path:gmatch("[^/]+") do
|
||||
table.insert(parts, part)
|
||||
end
|
||||
local i = 1
|
||||
while i <= #parts do
|
||||
if parts[i] == "." then
|
||||
table.remove(parts, i)
|
||||
elseif parts[i] == ".." then
|
||||
table.remove(parts, i)
|
||||
i = i - 1
|
||||
if i > 0 then
|
||||
table.remove(parts, i)
|
||||
else
|
||||
i = 1
|
||||
end
|
||||
else
|
||||
i = i + i
|
||||
end
|
||||
end
|
||||
return parts
|
||||
end
|
||||
|
||||
local function findNode(path, create)
|
||||
checkArg(1, path, "string")
|
||||
local parts = segments(path)
|
||||
local node = mtab
|
||||
for i = 1, #parts do
|
||||
if not node.children[parts[i]] then
|
||||
if create then
|
||||
node.children[parts[i]] = {children={}, parent=node}
|
||||
else
|
||||
return node, table.concat(parts, "/", i)
|
||||
end
|
||||
end
|
||||
node = node.children[parts[i]]
|
||||
end
|
||||
return node
|
||||
end
|
||||
|
||||
local function removeEmptyNodes(node)
|
||||
while node and node.parent and not node.fs and not next(node.children) do
|
||||
for k, c in pairs(node.parent.children) do
|
||||
if c == node then
|
||||
node.parent.children[k] = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
node = node.parent
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
driver.fs = {}
|
||||
|
||||
function driver.fs.mount(fs, path)
|
||||
if fs and path then
|
||||
checkArg(1, fs, "string")
|
||||
local node = findNode(path, true)
|
||||
if node.fs then
|
||||
return nil, "another filesystem is already mounted here"
|
||||
end
|
||||
node.fs = fs
|
||||
else
|
||||
local function path(node)
|
||||
local result = "/"
|
||||
while node and node.parent do
|
||||
for name, child in pairs(node.parent.children) do
|
||||
if child == node then
|
||||
result = "/" .. name .. result
|
||||
break
|
||||
end
|
||||
end
|
||||
node = node.parent
|
||||
end
|
||||
return result
|
||||
end
|
||||
local queue = {mtab}
|
||||
return function()
|
||||
if #queue == 0 then
|
||||
return nil
|
||||
else
|
||||
while true do
|
||||
local node = table.remove(queue)
|
||||
for _, child in pairs(node.children) do
|
||||
table.insert(queue, child)
|
||||
end
|
||||
if node.fs then
|
||||
return node.fs, path(node)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function driver.fs.umount(fsOrPath)
|
||||
local node, rest = findNode(fsOrPath)
|
||||
if not rest and node.fs then
|
||||
node.fs = nil
|
||||
removeEmptyNodes(node)
|
||||
return true
|
||||
else
|
||||
local queue = {mtab}
|
||||
for fs, path in driver.fs.mount() do
|
||||
if fs == fsOrPath then
|
||||
local node = findNode(path)
|
||||
node.fs = nil
|
||||
removeEmptyNodes(node)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function driver.fs.spaceTotal(path)
|
||||
local node, rest = findNode(path)
|
||||
if node.fs then
|
||||
return send(node.fs, "fs.spaceTotal")
|
||||
else
|
||||
return nil, "no such device"
|
||||
end
|
||||
end
|
||||
|
||||
function driver.fs.spaceUsed(path)
|
||||
local node, rest = findNode(path)
|
||||
if node.fs then
|
||||
return send(node.fs, "fs.spaceUsed")
|
||||
else
|
||||
return nil, "no such device"
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function driver.fs.exists(path)
|
||||
local node, rest = findNode(path)
|
||||
if not rest then -- virtual directory
|
||||
return true
|
||||
end
|
||||
if node.fs then
|
||||
return send(node.fs, "fs.exists", rest)
|
||||
end
|
||||
end
|
||||
|
||||
function driver.fs.size(path)
|
||||
local node, rest = findNode(path)
|
||||
if node.fs and rest then
|
||||
return send(node.fs, "fs.size", rest)
|
||||
end
|
||||
return 0 -- no such file or directory or it's a virtual directory
|
||||
end
|
||||
|
||||
function driver.fs.dir(path)
|
||||
local node, rest = findNode(path)
|
||||
if not node.fs and rest then
|
||||
return nil, "no such file or directory"
|
||||
end
|
||||
local result
|
||||
if node.fs then
|
||||
result = table.pack(send(node.fs, "fs.list", rest or ""))
|
||||
if not result[1] then
|
||||
return nil, result[2]
|
||||
end
|
||||
else
|
||||
result = {}
|
||||
end
|
||||
if not rest then
|
||||
for k, _ in pairs(node.children) do
|
||||
table.insert(result, k .. "/")
|
||||
end
|
||||
end
|
||||
table.sort(result)
|
||||
return table.unpack(result)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function driver.fs.remove(path)
|
||||
local node, rest = findNode(path)
|
||||
if node.fs and rest then
|
||||
return send(node.fs, "fs.remove", rest)
|
||||
end
|
||||
end
|
||||
|
||||
function driver.fs.rename(oldPath, newPath)
|
||||
local oldNode, oldRest = findNode(oldPath)
|
||||
local newNode, newRest = findNode(newPath)
|
||||
if oldNode.fs and oldRest and newNode.fs and newRest then
|
||||
if oldNode.fs == newNode.fs then
|
||||
return send(oldNode.fs, "fs.rename", oldRest, newRest)
|
||||
else
|
||||
local result, reason = driver.fs.copy(oldPath, newPath)
|
||||
if result then
|
||||
return driver.fs.remove(oldPath)
|
||||
else
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function driver.fs.copy(fromPath, toPath)
|
||||
--[[ TODO ]]
|
||||
return nil, "not implemented"
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local file = {}
|
||||
|
||||
function file:close()
|
||||
if self.handle then
|
||||
self:flush()
|
||||
return self.stream:close()
|
||||
end
|
||||
end
|
||||
|
||||
function file:flush()
|
||||
if not self.handle then
|
||||
return nil, "file is closed"
|
||||
end
|
||||
|
||||
if #self.buffer > 0 then
|
||||
local result, reason = self.stream:write(self.buffer)
|
||||
if result then
|
||||
self.buffer = ""
|
||||
else
|
||||
if reason then
|
||||
return nil, reason
|
||||
else
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function file:lines(...)
|
||||
local args = table.pack(...)
|
||||
return function()
|
||||
local result = table.pack(self:read(table.unpack(args, 1, args.n)))
|
||||
if not result[1] and result[2] then
|
||||
error(result[2])
|
||||
end
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
end
|
||||
|
||||
function file:read(...)
|
||||
if not self.handle then
|
||||
return nil, "file is closed"
|
||||
end
|
||||
|
||||
local function readChunk()
|
||||
local result, reason = self.stream:read(self.bufferSize)
|
||||
if result then
|
||||
self.buffer = self.buffer .. result
|
||||
return self
|
||||
else -- error or eof
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
|
||||
local function readBytesOrChars(n)
|
||||
local len, sub
|
||||
if self.mode == "r" then
|
||||
len = string.len
|
||||
sub = string.sub
|
||||
else
|
||||
assert(self.mode == "rb")
|
||||
len = rawlen
|
||||
sub = string.bsub
|
||||
end
|
||||
local result = ""
|
||||
repeat
|
||||
if len(self.buffer) == 0 then
|
||||
local result, reason = readChunk()
|
||||
if not result then
|
||||
if reason then
|
||||
return nil, reason
|
||||
else -- eof
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
local left = n - len(result)
|
||||
result = result .. sub(self.buffer, 1, left)
|
||||
self.buffer = sub(self.buffer, left + 1)
|
||||
until len(result) == n
|
||||
return result
|
||||
end
|
||||
|
||||
local function readLine(chop)
|
||||
local start = 1
|
||||
while true do
|
||||
local l = self.buffer:find("\n", start, true)
|
||||
if l then
|
||||
local result = self.buffer:bsub(1, l + (chop and -1 or 0))
|
||||
self.buffer = self.buffer:bsub(l + 1)
|
||||
return result
|
||||
else
|
||||
start = #self.buffer
|
||||
local result, reason = readChunk()
|
||||
if not result then
|
||||
if reason then
|
||||
return nil, reason
|
||||
else -- eof
|
||||
local result = #self.buffer > 0 and self.buffer or nil
|
||||
self.buffer = ""
|
||||
return result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function readAll()
|
||||
repeat
|
||||
local result, reason = readChunk()
|
||||
if not result and reason then
|
||||
return nil, reason
|
||||
end
|
||||
until not result -- eof
|
||||
local result = self.buffer
|
||||
self.buffer = ""
|
||||
return result
|
||||
end
|
||||
|
||||
local function read(n, format)
|
||||
if type(format) == "number" then
|
||||
return readBytesOrChars(format)
|
||||
else
|
||||
if not type(format) == "string" or format:sub(1, 1) ~= "*" then
|
||||
error("bad argument #" .. n .. " (invalid option)")
|
||||
end
|
||||
format = format:sub(2, 2)
|
||||
if format == "n" then
|
||||
--[[ TODO ]]
|
||||
error("not implemented")
|
||||
elseif format == "l" then
|
||||
return readLine(true)
|
||||
elseif format == "L" then
|
||||
return readLine(false)
|
||||
elseif format == "a" then
|
||||
return readAll()
|
||||
else
|
||||
error("bad argument #" .. n .. " (invalid format)")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local result = {}
|
||||
local formats = table.pack(...)
|
||||
if formats.n == 0 then
|
||||
return readLine(true)
|
||||
end
|
||||
for i = 1, formats.n do
|
||||
table.insert(result, read(i, formats[i]))
|
||||
end
|
||||
return table.unpack(result)
|
||||
end
|
||||
|
||||
function file:seek(whence, offset)
|
||||
if not self.handle then
|
||||
return nil, "file is closed"
|
||||
end
|
||||
|
||||
whence = tostring(whence or "cur")
|
||||
assert(whence == "set" or whence == "cur" or whence == "end",
|
||||
"bad argument #1 (set, cur or end expected, got " .. whence .. ")")
|
||||
offset = offset or 0
|
||||
checkArg(2, offset, "number")
|
||||
assert(math.floor(offset) == offset, "bad argument #2 (not an integer)")
|
||||
|
||||
if whence == "cur" and offset ~= 0 then
|
||||
offset = offset - #(self.buffer or "")
|
||||
end
|
||||
local result, reason = self.stream:seek(whence, offset)
|
||||
if result then
|
||||
if offset ~= 0 then
|
||||
self.buffer = ""
|
||||
elseif whence == "cur" then
|
||||
result = result - #self.buffer
|
||||
end
|
||||
end
|
||||
return result, reason
|
||||
end
|
||||
|
||||
function file:setvbuf(mode, size)
|
||||
if not self.handle then
|
||||
return nil, "file is closed"
|
||||
end
|
||||
|
||||
mode = mode or self.bufferMode
|
||||
size = size or self.bufferSize
|
||||
|
||||
assert(mode == "no" or mode == "full" or mode == "line",
|
||||
"bad argument #1 (no, full or line expected, got " .. tostring(mode) .. ")")
|
||||
assert(mode == "no" or type(size) == "number",
|
||||
"bad argument #2 (number expected, got " .. type(size) .. ")")
|
||||
|
||||
self:flush()
|
||||
self.bufferMode = mode
|
||||
self.bufferSize = mode == "no" and 0 or size
|
||||
|
||||
return self.bufferMode, self.bufferSize
|
||||
end
|
||||
|
||||
function file:write(...)
|
||||
if not self.handle then
|
||||
return nil, "file is closed"
|
||||
end
|
||||
|
||||
local args = table.pack(...)
|
||||
for i = 1, args.n do
|
||||
if type(args[i]) == "number" then
|
||||
args[i] = tostring(args[i])
|
||||
end
|
||||
checkArg(i, args[i], "string")
|
||||
end
|
||||
|
||||
for i = 1, args.n do
|
||||
local arg = args[i]
|
||||
local result, reason
|
||||
|
||||
if (self.bufferMode == "full" or self.bufferMode == "line") and
|
||||
self.bufferSize - #self.buffer < #arg
|
||||
then
|
||||
result, reason = self:flush()
|
||||
if not result then
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
|
||||
if self.bufferMode == "full" then
|
||||
if #arg > self.bufferSize then
|
||||
result, reason = self.stream:write(arg)
|
||||
else
|
||||
self.buffer = self.buffer .. arg
|
||||
result = self
|
||||
end
|
||||
|
||||
elseif self.bufferMode == "line" then
|
||||
local l
|
||||
repeat
|
||||
local idx = self.buffer:find("\n", l or 1, true)
|
||||
if idx then
|
||||
l = idx
|
||||
end
|
||||
until not idx
|
||||
if l then
|
||||
result, reason = self:flush()
|
||||
if not result then
|
||||
return nil, reason
|
||||
end
|
||||
result, reason = self.stream:write(arg:bsub(1, l))
|
||||
if not result then
|
||||
return nil, reason
|
||||
end
|
||||
arg = arg:bsub(l + 1)
|
||||
end
|
||||
if #arg > self.bufferSize then
|
||||
result, reason = self.stream:write(arg)
|
||||
else
|
||||
self.buffer = arg
|
||||
result = self
|
||||
end
|
||||
|
||||
else -- no
|
||||
result, reason = self.stream:write(arg)
|
||||
end
|
||||
|
||||
if not result then
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function file.new(fs, handle, mode, stream, nogc)
|
||||
local result = {
|
||||
fs = fs,
|
||||
handle = handle,
|
||||
mode = mode,
|
||||
buffer = "",
|
||||
bufferSize = math.min(8 * 1024, os.totalMemory() / 16),
|
||||
bufferMode = "full"
|
||||
}
|
||||
result.stream = setmetatable(stream, {__index = {file = result}})
|
||||
|
||||
local metatable = {
|
||||
__index = file,
|
||||
__metatable = "file"
|
||||
}
|
||||
if not nogc then
|
||||
metatable.__gc = function(self)
|
||||
-- file.close does a syscall, which yields, and that's not possible in
|
||||
-- the __gc metamethod. So we start a timer to do the yield/cleanup.
|
||||
event.timer(0, function()
|
||||
self:close()
|
||||
end)
|
||||
end
|
||||
end
|
||||
return setmetatable(result, metatable)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local fileStream = {}
|
||||
|
||||
function fileStream:close()
|
||||
send(self.file.fs, "fs.close", self.file.handle)
|
||||
self.file.handle = nil
|
||||
end
|
||||
|
||||
function fileStream:read(n)
|
||||
return send(self.file.fs, "fs.read", self.file.handle, n)
|
||||
end
|
||||
|
||||
function fileStream:seek(whence, offset)
|
||||
return send(self.file.fs, "fs.seek", self.file.handle, whence, offset)
|
||||
end
|
||||
|
||||
function fileStream:write(str)
|
||||
return send(self.file.fs, "fs.write", self.file.handle, str)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function driver.fs.open(path, mode)
|
||||
mode = tostring(mode or "r")
|
||||
checkArg(2, mode, "string")
|
||||
assert(({r=true, rb=true, w=true, wb=true, a=true, ab=true})[mode],
|
||||
"bad argument #2 (r[b], w[b] or a[b] expected, got " .. mode .. ")")
|
||||
|
||||
local node, rest = findNode(path)
|
||||
if not node.fs or not rest then
|
||||
return nil, "file not found"
|
||||
end
|
||||
|
||||
local handle, reason = send(node.fs, "fs.open", rest, mode)
|
||||
if not handle then
|
||||
return nil, reason
|
||||
end
|
||||
|
||||
return file.new(node.fs, handle, mode, fileStream)
|
||||
end
|
||||
|
||||
function driver.fs.type(object)
|
||||
if type(object) == "table" then
|
||||
if getmetatable(object) == "file" then
|
||||
if object.handle then
|
||||
return "file"
|
||||
else
|
||||
return "closed file"
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function loadfile(filename, env)
|
||||
local file, reason = driver.fs.open(filename)
|
||||
if not file then
|
||||
return nil, reason
|
||||
end
|
||||
local source, reason = file:read("*a")
|
||||
file:close()
|
||||
if not source then
|
||||
return nil, reason
|
||||
end
|
||||
return load(source, "=" .. filename, env)
|
||||
end
|
||||
|
||||
function dofile(filename)
|
||||
local program, reason = loadfile(filename)
|
||||
if not program then
|
||||
return error(reason)
|
||||
end
|
||||
return program()
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
io = {}
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local stdinStream = {}
|
||||
|
||||
function stdinStream:close()
|
||||
return nil, "cannot close standard file"
|
||||
end
|
||||
|
||||
function stdinStream:read(n)
|
||||
|
||||
end
|
||||
|
||||
function stdinStream:seek(whence, offset)
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
|
||||
function stdinStream:write(str)
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
|
||||
local stdoutStream = {}
|
||||
|
||||
function stdoutStream:close()
|
||||
return nil, "cannot close standard file"
|
||||
end
|
||||
|
||||
function stdoutStream:read(n)
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
|
||||
function stdoutStream:seek(whence, offset)
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
|
||||
function stdoutStream:write(str)
|
||||
term.write(str)
|
||||
return self
|
||||
end
|
||||
|
||||
io.stdin = file.new(nil, "stdin", "r", stdinStream, true)
|
||||
io.stdout = file.new(nil, "stdout", "w", stdoutStream, true)
|
||||
io.stderr = io.stdout
|
||||
|
||||
io.stdout:setvbuf("no")
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local input, output = io.stdin, io.stdout
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function io.close(file)
|
||||
return (file or io.output()):close()
|
||||
end
|
||||
|
||||
function io.flush()
|
||||
return io.output():flush()
|
||||
end
|
||||
|
||||
function io.input(file)
|
||||
if file then
|
||||
if type(file) == "string" then
|
||||
local result, reason = io.open(file)
|
||||
if not result then
|
||||
error(reason)
|
||||
end
|
||||
input = result
|
||||
elseif io.type(file) then
|
||||
input = file
|
||||
else
|
||||
error("bad argument #1 (string or file expected, got " .. type(file) .. ")")
|
||||
end
|
||||
end
|
||||
return input
|
||||
end
|
||||
|
||||
function io.lines(filename, ...)
|
||||
if filename then
|
||||
local result, reason = io.open(filename)
|
||||
if not result then
|
||||
error(reason)
|
||||
end
|
||||
local args = table.pack(...)
|
||||
return function()
|
||||
local result = table.pack(file:read(table.unpack(args, 1, args.n)))
|
||||
if not result[1] then
|
||||
if result[2] then
|
||||
error(result[2])
|
||||
else -- eof
|
||||
file:close()
|
||||
return nil
|
||||
end
|
||||
end
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
else
|
||||
return io.input():lines()
|
||||
end
|
||||
end
|
||||
|
||||
io.open = driver.fs.open
|
||||
|
||||
function io.output(file)
|
||||
if file then
|
||||
if type(file) == "string" then
|
||||
local result, reason = io.open(file, "w")
|
||||
if not result then
|
||||
error(reason)
|
||||
end
|
||||
output = result
|
||||
elseif io.type(file) then
|
||||
output = file
|
||||
else
|
||||
error("bad argument #1 (string or file expected, got " .. type(file) .. ")")
|
||||
end
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
-- TODO io.popen = function(prog, mode) end
|
||||
|
||||
function io.read(...)
|
||||
return io.input():read(...)
|
||||
end
|
||||
|
||||
-- TODO io.tmpfile = function() end
|
||||
|
||||
io.type = driver.fs.type
|
||||
|
||||
function io.write(...)
|
||||
return io.output():write(...)
|
||||
end
|
||||
|
||||
function print(...)
|
||||
local args = table.pack(...)
|
||||
for i = 1, args.n do
|
||||
local arg = tostring(args[i])
|
||||
if i > 1 then
|
||||
arg = "\t" .. arg
|
||||
end
|
||||
io.stdout:write(arg)
|
||||
end
|
||||
io.stdout:write("\n")
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
os.remove = driver.fs.remove
|
||||
os.rename = driver.fs.rename
|
||||
|
||||
-- TODO os.tmpname = function() end
|
@ -1,446 +0,0 @@
|
||||
local mtab = {children={}}
|
||||
|
||||
local function segments(path)
|
||||
path = path:gsub("\\", "/")
|
||||
repeat local n; path, n = path:gsub("//", "/") until n == 0
|
||||
local parts = {}
|
||||
for part in path:gmatch("[^/]+") do
|
||||
table.insert(parts, part)
|
||||
end
|
||||
local i = 1
|
||||
while i <= #parts do
|
||||
if parts[i] == "." then
|
||||
table.remove(parts, i)
|
||||
elseif parts[i] == ".." then
|
||||
table.remove(parts, i)
|
||||
i = i - 1
|
||||
if i > 0 then
|
||||
table.remove(parts, i)
|
||||
else
|
||||
i = 1
|
||||
end
|
||||
else
|
||||
i = i + i
|
||||
end
|
||||
end
|
||||
return parts
|
||||
end
|
||||
|
||||
local function findNode(path, create)
|
||||
checkArg(1, path, "string")
|
||||
local parts = segments(path)
|
||||
local node = mtab
|
||||
for i = 1, #parts do
|
||||
if not node.children[parts[i]] then
|
||||
if create then
|
||||
node.children[parts[i]] = {children={}, parent=node}
|
||||
else
|
||||
return node, table.concat(parts, "/", i)
|
||||
end
|
||||
end
|
||||
node = node.children[parts[i]]
|
||||
end
|
||||
return node
|
||||
end
|
||||
|
||||
local function removeEmptyNodes(node)
|
||||
while node and node.parent and not node.fs and not next(node.children) do
|
||||
for k, c in pairs(node.parent.children) do
|
||||
if c == node then
|
||||
node.parent.children[k] = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
node = node.parent
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
driver.fs = {}
|
||||
|
||||
function driver.fs.mount(fs, path)
|
||||
checkArg(1, fs, "string")
|
||||
local node = findNode(path, true)
|
||||
if node.fs then
|
||||
return nil, "another filesystem is already mounted here"
|
||||
end
|
||||
node.fs = fs
|
||||
end
|
||||
|
||||
function driver.fs.umount(fsOrPath)
|
||||
local node, rest = findNode(fsOrPath)
|
||||
if not rest and node.fs then
|
||||
node.fs = nil
|
||||
removeEmptyNodes(node)
|
||||
return true
|
||||
else
|
||||
local queue = {mtab}
|
||||
repeat
|
||||
local node = table.remove(queue)
|
||||
if node.fs == fsOrPath then
|
||||
node.fs = nil
|
||||
removeEmptyNodes(node)
|
||||
return true
|
||||
end
|
||||
for _, child in ipairs(node.children) do
|
||||
table.insert(queue, child)
|
||||
end
|
||||
until #queue == 0
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function driver.fs.exists(path)
|
||||
local node, rest = findNode(path)
|
||||
if not rest then -- virtual directory
|
||||
return true
|
||||
end
|
||||
if node.fs then
|
||||
return sendToNode(node.fs, "fs.exists", rest)
|
||||
end
|
||||
end
|
||||
|
||||
function driver.fs.size(path)
|
||||
local node, rest = findNode(path)
|
||||
if node.fs and rest then
|
||||
return sendToNode(node.fs, "fs.size", rest)
|
||||
end
|
||||
return 0 -- no such file or directory or virtual directory
|
||||
end
|
||||
|
||||
function driver.fs.listdir(path)
|
||||
local node, rest = findNode(path)
|
||||
if not node.fs and rest then
|
||||
return nil, "no such file or directory"
|
||||
end
|
||||
local result
|
||||
if node.fs then
|
||||
result = {sendToNode(node.fs, "fs.list", rest or "")}
|
||||
if not result[1] then
|
||||
return nil, result[2]
|
||||
end
|
||||
else
|
||||
result = {}
|
||||
end
|
||||
if not rest then
|
||||
for k, _ in pairs(node.children) do
|
||||
table.insert(result, k .. "/")
|
||||
end
|
||||
end
|
||||
table.sort(result)
|
||||
return table.unpack(result)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function driver.fs.remove(path)
|
||||
local node, rest = findNode(path)
|
||||
if node.fs and rest then
|
||||
return sendToNode(node.fs, "fs.remove", rest)
|
||||
end
|
||||
end
|
||||
|
||||
function driver.fs.rename(oldPath, newPath)
|
||||
--[[ TODO moving between file systems will require actual data copying...
|
||||
local node, rest = findNode(path)
|
||||
local newNode, newRest = findNode(newPath)
|
||||
if node.fs and rest and newNode and newRest then
|
||||
return sendToNode(node.fs, "fs.rename", rest)
|
||||
end
|
||||
]]
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local file = {}
|
||||
|
||||
function file.close(f)
|
||||
if f.handle then
|
||||
f:flush()
|
||||
sendToNode(f.fs, "fs.close", f.handle)
|
||||
f.handle = nil
|
||||
end
|
||||
end
|
||||
|
||||
function file.flush(f)
|
||||
if not f.handle then
|
||||
return nil, "file is closed"
|
||||
end
|
||||
if #(f.buffer or "") > 0 then
|
||||
local result, reason = sendToNode(f.fs, "fs.write", f.handle, f.buffer)
|
||||
if result then
|
||||
f.buffer = nil
|
||||
else
|
||||
if reason then
|
||||
return nil, reason
|
||||
else
|
||||
return nil, "invalid file"
|
||||
end
|
||||
end
|
||||
end
|
||||
return f
|
||||
end
|
||||
|
||||
function file.read(f, ...)
|
||||
if not f.handle then
|
||||
return nil, "file is closed"
|
||||
end
|
||||
local function readChunk()
|
||||
local read, reason = sendToNode(f.fs, "fs.read", f.handle, f.bsize)
|
||||
if read then
|
||||
f.buffer = (f.buffer or "") .. read
|
||||
return true
|
||||
else
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
local function readBytes(n)
|
||||
while #(f.buffer or "") < n do
|
||||
local result, reason = readChunk()
|
||||
if not result then
|
||||
if reason then
|
||||
return nil, reason
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
local result
|
||||
if f.buffer then
|
||||
if #f.buffer > format then
|
||||
result = f.buffer:bsub(1, format)
|
||||
f.buffer = f.buffer:bsub(format + 1)
|
||||
else
|
||||
result = f.buffer
|
||||
f.buffer = nil
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
local function readLine(chop)
|
||||
while true do
|
||||
local l = (f.buffer or ""):find("\n", 1, true)
|
||||
if l then
|
||||
local rl = l + (chop and -1 or 0)
|
||||
local line = f.buffer:bsub(1, rl)
|
||||
f.buffer = f.buffer:bsub(l + 1)
|
||||
return line
|
||||
else
|
||||
local result, reason = readChunk()
|
||||
if not result then
|
||||
if reason then
|
||||
return nil, reason
|
||||
else
|
||||
local line = f.buffer
|
||||
f.buffer = nil
|
||||
return line
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local function readAll()
|
||||
repeat
|
||||
local result, reason = readChunk()
|
||||
if not result and reason then
|
||||
return nil, reason
|
||||
end
|
||||
until not result
|
||||
local result = f.buffer or ""
|
||||
f.buffer = nil
|
||||
return result
|
||||
end
|
||||
local function read(n, format)
|
||||
if type(format) == "number" then
|
||||
return readBytes(format)
|
||||
else
|
||||
if not type(format) == "string" or format:sub(1, 1) ~= "*" then
|
||||
error("bad argument #" .. n .. " (invalid option)")
|
||||
end
|
||||
format = format:sub(2, 2)
|
||||
if format == "n" then
|
||||
error("not implemented")
|
||||
elseif format == "l" then
|
||||
return readLine(true)
|
||||
elseif format == "L" then
|
||||
return readLine(false)
|
||||
elseif format == "a" then
|
||||
return readAll()
|
||||
else
|
||||
error("bad argument #" .. n .. " (invalid format)")
|
||||
end
|
||||
end
|
||||
end
|
||||
local results = {}
|
||||
local formats = {...}
|
||||
if #formats == 0 then
|
||||
return readLine(true)
|
||||
end
|
||||
for n, format in ipairs(formats) do
|
||||
table.insert(results, read(n, format))
|
||||
end
|
||||
return table.unpack(results)
|
||||
end
|
||||
|
||||
function file.seek(f, whence, offset)
|
||||
if not f.handle then
|
||||
return nil, "file is closed"
|
||||
end
|
||||
whence = whence or "cur"
|
||||
assert(whence == "set" or whence == "cur" or whence == "end",
|
||||
"bad argument #1 (set, cur or end expected, got " .. tostring(whence) .. ")")
|
||||
offset = offset or 0
|
||||
checkArg(2, offset, "number")
|
||||
assert(math.floor(offset) == offset, "bad argument #2 (not an integer)")
|
||||
|
||||
if whence == "cur" and offset ~= 0 then
|
||||
offset = offset - #(f.buffer or "")
|
||||
end
|
||||
local result, reason = sendToNode(f.fs, "fs.seek", f.handle, whence, offset)
|
||||
if result then
|
||||
if offset ~= 0 then
|
||||
f.buffer = nil
|
||||
elseif whence == "cur" then
|
||||
result = result - #(f.buffer or "")
|
||||
end
|
||||
end
|
||||
return result, reason
|
||||
end
|
||||
|
||||
function file.setvbuf(f, mode, size)
|
||||
if not f.handle then
|
||||
return nil, "file is closed"
|
||||
end
|
||||
assert(mode == "no" or mode == "full" or mode == "line",
|
||||
"bad argument #1 (no, full or line expected, got " .. tostring(mode) .. ")")
|
||||
assert(mode == "no" or type(size) == "number",
|
||||
"bad argument #2 (number expected, got " .. type(size) .. ")")
|
||||
f:flush()
|
||||
f.bmode = mode
|
||||
f.bsize = size
|
||||
end
|
||||
|
||||
function file.write(f, ...)
|
||||
if not f.handle then
|
||||
return nil, "file is closed"
|
||||
end
|
||||
local args = {...}
|
||||
for n, arg in ipairs(args) do
|
||||
if type(arg) == "number" then
|
||||
args[n] = tostring(arg)
|
||||
end
|
||||
checkArg(n, arg, "string")
|
||||
end
|
||||
for _, arg in ipairs(args) do
|
||||
local result, reason
|
||||
if f.bmode == "full" or #(f.buffer or "") + #arg > f.bsize then
|
||||
if #(f.buffer or "") + #arg > f.bsize then
|
||||
result, reason = f:flush()
|
||||
if result then
|
||||
if #arg > f.bsize then
|
||||
result, reason = sendToNode(f.fs, "fs.write", f.handle, arg)
|
||||
else
|
||||
f.buffer = arg
|
||||
end
|
||||
end
|
||||
else
|
||||
f.buffer = (f.buffer or "") .. arg
|
||||
result = f
|
||||
end
|
||||
elseif f.bmode == "line" then
|
||||
repeat
|
||||
local l = (f.buffer or ""):find("\n", 1, true)
|
||||
if l then
|
||||
result, reason = f:flush()
|
||||
if result then
|
||||
result, reason = sendToNode(f.fs, "fs.write", f.handle, arg:bsub(1, l))
|
||||
if result then
|
||||
f.buffer = arg:bsub(l + 1)
|
||||
end
|
||||
end
|
||||
else
|
||||
f.buffer = f.buffer .. arg
|
||||
end
|
||||
until not l or not result
|
||||
else
|
||||
if #(f.buffer or "") > 0 then
|
||||
result, reason = f:flush()
|
||||
if not result then
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
result, reason = sendToNode(f.fs, "fs.write", f.handle, arg)
|
||||
end
|
||||
if not result then
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
return f
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function driver.fs.open(path, mode)
|
||||
mode = mode or "r"
|
||||
checkArg(2, mode, "string")
|
||||
assert(({r=true, rb=true, w=true, wb=true, a=true, ab=true})[mode],
|
||||
"bad argument #2 (r[b], w[b] or a[b] expected, got " .. tostring(mode) .. ")")
|
||||
local node, rest = findNode(path)
|
||||
if not node.fs or not rest then -- files can only be in file systems
|
||||
return nil, "file not found"
|
||||
end
|
||||
local handle, reason = sendToNode(node.fs, "fs.open", rest or "", mode)
|
||||
if not handle then
|
||||
return nil, reason
|
||||
end
|
||||
return setmetatable({
|
||||
fs = node.fs,
|
||||
handle = handle,
|
||||
bsize = math.min(8 * 1024, os.totalMemory() / 16),
|
||||
bmode = "full"
|
||||
}, {
|
||||
__index = file,
|
||||
__gc = function(f)
|
||||
-- file.close does a syscall, which yields, and that's not possible in
|
||||
-- the __gc metamethod. So we start a timer to do the yield/cleanup.
|
||||
event.timer(0, function()
|
||||
file.close(f)
|
||||
end)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function driver.fs.type(f)
|
||||
local info = getFileInfo(f, true)
|
||||
if not info then
|
||||
return nil
|
||||
elseif not info.handle then
|
||||
return "closed file"
|
||||
else
|
||||
return "file"
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function loadfile(file, env)
|
||||
local f, reason = driver.fs.open(file)
|
||||
if not f then
|
||||
return nil, reason
|
||||
end
|
||||
local source, reason = f:read("*a")
|
||||
f:close()
|
||||
if not source then
|
||||
return nil, reason
|
||||
end
|
||||
return load(source, "=" .. file, env)
|
||||
end
|
||||
|
||||
function dofile(file)
|
||||
local f, reason = loadfile(file)
|
||||
if not f then
|
||||
return nil, reason
|
||||
end
|
||||
return f()
|
||||
end
|
@ -1,48 +1,52 @@
|
||||
driver.gpu = {}
|
||||
|
||||
function driver.gpu.setResolution(gpu, screen, w, h)
|
||||
sendToNode(gpu, "gpu.resolution=", screen, w, h)
|
||||
end
|
||||
|
||||
function driver.gpu.getResolution(gpu, screen)
|
||||
return sendToNode(gpu, "gpu.resolution", screen)
|
||||
end
|
||||
|
||||
function driver.gpu.getResolutions(gpu, screen)
|
||||
return sendToNode(gpu, "gpu.resolutions", screen)
|
||||
end
|
||||
|
||||
function driver.gpu.set(gpu, screen, col, row, value)
|
||||
sendToNode(gpu, "gpu.set", screen, col, row, value)
|
||||
end
|
||||
|
||||
function driver.gpu.fill(gpu, screen, col, row, w, h, value)
|
||||
sendToNode(gpu, "gpu.fill", screen, col, row, w, h, value:sub(1, 1))
|
||||
end
|
||||
|
||||
function driver.gpu.copy(gpu, screen, col, row, w, h, tx, ty)
|
||||
sendToNode(gpu, "gpu.copy", screen, col, row, w, h, tx, ty)
|
||||
end
|
||||
|
||||
function driver.gpu.bind(gpu, screen)
|
||||
return {
|
||||
setResolution = function(w, h)
|
||||
driver.gpu.setResolution(gpu, screen, w, h)
|
||||
end,
|
||||
getResolution = function()
|
||||
return driver.gpu.getResolution(gpu, screen)
|
||||
end,
|
||||
getResolutions = function()
|
||||
return driver.gpu.getResolutions(gpu, screen)
|
||||
end,
|
||||
set = function(col, row, value)
|
||||
driver.gpu.set(gpu, screen, col, row, value)
|
||||
end,
|
||||
fill = function(col, ro, w, h, value)
|
||||
driver.gpu.fill(gpu, screen, col, ro, w, h, value)
|
||||
end,
|
||||
copy = function(col, row, w, h, tx, ty)
|
||||
driver.gpu.copy(gpu, screen, col, row, w, h, tx, ty)
|
||||
end
|
||||
}
|
||||
end
|
||||
checkArg(1, gpu, "string")
|
||||
checkArg(2, screen, "string")
|
||||
return send(gpu, "gpu.bind", screen)
|
||||
end
|
||||
|
||||
function driver.gpu.resolution(gpu, w, h)
|
||||
checkArg(1, gpu, "string")
|
||||
if w and h then
|
||||
checkArg(2, w, "number")
|
||||
checkArg(3, h, "number")
|
||||
return send(gpu, "gpu.resolution=", w, h)
|
||||
else
|
||||
return send(gpu, "gpu.resolution")
|
||||
end
|
||||
end
|
||||
|
||||
function driver.gpu.resolutions(gpu)
|
||||
checkArg(1, gpu, "string")
|
||||
return send(gpu, "gpu.resolutions")
|
||||
end
|
||||
|
||||
function driver.gpu.set(gpu, col, row, value)
|
||||
checkArg(1, gpu, "string")
|
||||
checkArg(2, col, "number")
|
||||
checkArg(3, row, "number")
|
||||
checkArg(4, value, "string")
|
||||
return send(gpu, "gpu.set", col, row, value)
|
||||
end
|
||||
|
||||
function driver.gpu.fill(gpu, col, row, w, h, value)
|
||||
checkArg(1, gpu, "string")
|
||||
checkArg(2, col, "number")
|
||||
checkArg(3, row, "number")
|
||||
checkArg(4, w, "number")
|
||||
checkArg(5, h, "number")
|
||||
checkArg(6, value, "string")
|
||||
return send(gpu, "gpu.fill", col, row, w, h, value:sub(1, 1))
|
||||
end
|
||||
|
||||
function driver.gpu.copy(gpu, col, row, w, h, tx, ty)
|
||||
checkArg(1, gpu, "string")
|
||||
checkArg(2, col, "number")
|
||||
checkArg(3, row, "number")
|
||||
checkArg(4, w, "number")
|
||||
checkArg(5, h, "number")
|
||||
checkArg(6, tx, "number")
|
||||
checkArg(7, ty, "number")
|
||||
return send(gpu, "gpu.copy", col, row, w, h, tx, ty)
|
||||
end
|
||||
|
@ -14,14 +14,14 @@ driver.redstone.sides.down = driver.redstone.sides.bottom
|
||||
local owner = os.address()
|
||||
|
||||
function driver.redstone.analogInput(card, side)
|
||||
sendToNode(card, owner, "redstone.input", side)
|
||||
send(card, owner, "redstone.input", side)
|
||||
end
|
||||
|
||||
function driver.redstone.analogOutput(card, side, value)
|
||||
if value then
|
||||
sendToNode(card, owner, "redstone.output=", side, tonumber(value))
|
||||
send(card, owner, "redstone.output=", side, tonumber(value))
|
||||
else
|
||||
return sendToNode(card, owner, "redstone.output", side)
|
||||
return send(card, owner, "redstone.output", side)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -91,6 +91,7 @@ local sandbox = {
|
||||
date = os.date,
|
||||
difftime = os.difftime,
|
||||
time = os.time,
|
||||
uptime = os.uptime,
|
||||
freeMemory = os.freeMemory,
|
||||
totalMemory = os.totalMemory,
|
||||
address = os.address,
|
||||
@ -98,6 +99,8 @@ local sandbox = {
|
||||
},
|
||||
|
||||
string = {
|
||||
breverse = string.breverse,
|
||||
bsub = string.bsub,
|
||||
byte = string.byte,
|
||||
char = string.char,
|
||||
dump = string.dump,
|
||||
@ -125,33 +128,27 @@ local sandbox = {
|
||||
}
|
||||
sandbox._G = sandbox
|
||||
|
||||
-- Note: 'write' will be replaced by init script/term API.
|
||||
function sandbox.write(...) end
|
||||
function sandbox.print(...)
|
||||
sandbox.write(...)
|
||||
sandbox.write("\n")
|
||||
end
|
||||
|
||||
function sandbox.load(code, source, env)
|
||||
return load(code, source, "t", env or sandbox)
|
||||
end
|
||||
|
||||
function sandbox.checkArg(n, have, ...)
|
||||
have = type(have)
|
||||
for _, want in pairs({...}) do
|
||||
if have == want then
|
||||
local want = table.pack(...)
|
||||
for i = 1, want.n do
|
||||
if have == want[i] then
|
||||
return
|
||||
end
|
||||
end
|
||||
local msg = "bad argument #" .. n .. " (" .. table.concat({...}, " or ") .. " expected, got " .. have .. ")"
|
||||
error(debug.traceback(msg, 3), 2)
|
||||
error(debug.traceback(msg, 2), 2)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
--[[ Install wrappers for coroutine management that reserves the first value
|
||||
returned by yields for internal stuff. Used for sleeping and message
|
||||
calls (sendToNode and its ilk) that happen synchronized (Server thread).
|
||||
calls (sendToAddress) that happen synchronized (Server thread).
|
||||
--]]
|
||||
local deadline = 0
|
||||
|
||||
@ -166,7 +163,7 @@ local function main(args)
|
||||
sandbox.driver.fs.mount(os.romAddress(), "/boot")
|
||||
local result, reason = sandbox.loadfile("/boot/init.lua")
|
||||
if not result then
|
||||
error(reason)
|
||||
error(reason, 0)
|
||||
end
|
||||
return coroutine.create(result)
|
||||
end
|
||||
@ -176,32 +173,32 @@ local function main(args)
|
||||
if not debug.gethook(co) then
|
||||
debug.sethook(co, checkDeadline, "", 10000)
|
||||
end
|
||||
local result = {coroutine.resume(co, table.unpack(args))}
|
||||
local result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n)))
|
||||
if result[1] then
|
||||
args = {coroutine.yield(result[2])} -- system yielded value
|
||||
args = table.pack(coroutine.yield(result[2])) -- system yielded value
|
||||
else
|
||||
error(result[2])
|
||||
error(result[2], 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function sandbox.coroutine.resume(co, ...)
|
||||
local args = {...}
|
||||
local args = table.pack(...)
|
||||
while true do
|
||||
if not debug.gethook(co) then -- don't reset counter
|
||||
debug.sethook(co, checkDeadline, "", 10000)
|
||||
end
|
||||
local result = {coroutine.resume(co, table.unpack(args))}
|
||||
local result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n)))
|
||||
checkDeadline()
|
||||
if result[1] then
|
||||
local isSystemYield = coroutine.status(co) ~= "dead" and result[2] ~= nil
|
||||
if isSystemYield then
|
||||
args = coroutine.yield(result[2])
|
||||
args = table.pack(coroutine.yield(result[2]))
|
||||
else
|
||||
return true, table.unpack(result, 3)
|
||||
return true, table.unpack(result, 3, result.n)
|
||||
end
|
||||
else -- error: result = (bool, string)
|
||||
return table.unpack(result)
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -210,6 +207,18 @@ function sandbox.coroutine.yield(...)
|
||||
return coroutine.yield(nil, ...)
|
||||
end
|
||||
|
||||
function sandbox.pcall(...)
|
||||
local result = table.pack(pcall(...))
|
||||
checkDeadline()
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
|
||||
function sandbox.xpcall(...)
|
||||
local result = table.pack(xpcall(...))
|
||||
checkDeadline()
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function sandbox.os.shutdown()
|
||||
@ -221,13 +230,13 @@ function sandbox.os.reboot()
|
||||
end
|
||||
|
||||
function sandbox.os.signal(name, timeout)
|
||||
local waitUntil = os.clock() + (type(timeout) == "number" and timeout or math.huge)
|
||||
while os.clock() < waitUntil do
|
||||
local signal = {coroutine.yield(waitUntil - os.clock())}
|
||||
if signal and (name == signal[1] or name == nil) then
|
||||
return table.unpack(signal)
|
||||
local waitUntil = os.uptime() + (type(timeout) == "number" and timeout or math.huge)
|
||||
repeat
|
||||
local signal = table.pack(coroutine.yield(waitUntil - os.uptime()))
|
||||
if signal.n > 0 and (name == signal[1] or name == nil) then
|
||||
return table.unpack(signal, 1, signal.n)
|
||||
end
|
||||
end
|
||||
until os.uptime() >= waitUntil
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
@ -239,9 +248,8 @@ function sandbox.driver.componentType(id)
|
||||
end
|
||||
|
||||
do
|
||||
local env = setmetatable({
|
||||
sendToNode = sendToNode,
|
||||
}, { __index = sandbox, __newindex = sandbox })
|
||||
local env = setmetatable({ send = sendToAddress },
|
||||
{ __index = sandbox, __newindex = sandbox })
|
||||
for name, code in pairs(drivers()) do
|
||||
local driver, reason = load(code, "=" .. name, "t", env)
|
||||
if not driver then
|
||||
@ -260,4 +268,4 @@ end
|
||||
-- JNLua converts the coroutine to a string immediately, so we can't get the
|
||||
-- traceback later. Because of that we have to do the error handling here.
|
||||
-- Also, yield once to allow initializing up to here to get a memory baseline.
|
||||
return pcall(main, {coroutine.yield()})
|
||||
return pcall(main, table.pack(coroutine.yield()))
|
||||
|
@ -55,7 +55,7 @@ function event.fire(name, ...)
|
||||
end
|
||||
local elapsed = {}
|
||||
for id, info in pairs(timers) do
|
||||
if info.after < os.clock() then
|
||||
if info.after < os.uptime() then
|
||||
table.insert(elapsed, info.callback)
|
||||
timers[id] = nil
|
||||
end
|
||||
@ -70,7 +70,7 @@ end
|
||||
|
||||
function event.timer(timeout, callback)
|
||||
local id = #timers + 1
|
||||
timers[id] = {after = os.clock() + timeout, callback = callback}
|
||||
timers[id] = {after = os.uptime() + timeout, callback = callback}
|
||||
return id
|
||||
end
|
||||
|
||||
@ -96,8 +96,9 @@ function event.error(message)
|
||||
end
|
||||
|
||||
function coroutine.sleep(seconds)
|
||||
seconds = seconds or math.huge
|
||||
checkArg(1, seconds, "number")
|
||||
local target = os.clock() + seconds
|
||||
local target = os.uptime() + seconds
|
||||
repeat
|
||||
local closest = target
|
||||
for _, info in pairs(timers) do
|
||||
@ -105,6 +106,6 @@ function coroutine.sleep(seconds)
|
||||
closest = info.after
|
||||
end
|
||||
end
|
||||
event.fire(os.signal(nil, closest - os.clock()))
|
||||
until os.clock() >= target
|
||||
event.fire(os.signal(nil, closest - os.uptime()))
|
||||
until os.uptime() >= target
|
||||
end
|
||||
|
@ -3,17 +3,144 @@ os.remove = driver.fs.remove
|
||||
os.rename = driver.fs.rename
|
||||
-- TODO os.tmpname = function() end
|
||||
|
||||
local function unavailable()
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
|
||||
io = {}
|
||||
-- TODO io.flush = function() end
|
||||
-- TODO io.lines = function(filename) end
|
||||
io.open = driver.fs.open
|
||||
|
||||
io.stdin = {handle="stdin"}
|
||||
|
||||
function io.stdin:close()
|
||||
return nil, "cannot close standard file"
|
||||
end
|
||||
|
||||
io.stdin.flush = unavailable
|
||||
|
||||
function io.stdin:lines(...)
|
||||
return function()
|
||||
local result = {self:read(...)}
|
||||
if not result[1] and result[2] then
|
||||
error(result[2])
|
||||
end
|
||||
return table.unpack(result)
|
||||
end
|
||||
end
|
||||
|
||||
function io.stdin:read(...)
|
||||
end
|
||||
|
||||
io.stdin.seek = unavailable
|
||||
io.stdin.setvbuf = unavailable
|
||||
io.stdin.write = unavailable
|
||||
|
||||
io.stdout = {handle="stdout"}
|
||||
|
||||
io.stdout.close = io.stdin.close
|
||||
|
||||
function io.stdout:flush()
|
||||
return self -- no-op
|
||||
end
|
||||
|
||||
io.stdout.lines = unavailable
|
||||
io.stdout.read = unavailable
|
||||
io.stdout.seek = unavailable
|
||||
io.stdout.setvbuf = unavailable
|
||||
|
||||
function io.stdout:write()
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
|
||||
io.stderr = io.stdout
|
||||
|
||||
io.lines = function(filename)
|
||||
local f = io.open(filename)
|
||||
return function()
|
||||
if f then
|
||||
local result, reason = f:read("*l")
|
||||
if result then
|
||||
return result
|
||||
else
|
||||
f:close()
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO io.popen = function(prog, mode) end
|
||||
io.read = driver.fs.read
|
||||
|
||||
-- TODO io.tmpfile = function() end
|
||||
|
||||
io.type = driver.fs.type
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local input, output = io.stdin, io.stdout
|
||||
|
||||
function io.close(file)
|
||||
(file or output):close()
|
||||
end
|
||||
|
||||
function io.flush()
|
||||
output:flush()
|
||||
end
|
||||
|
||||
function io.input(file)
|
||||
if file then
|
||||
if type(file) == "string" then
|
||||
local result, reason = io.open(file)
|
||||
if not result then
|
||||
error(reason)
|
||||
end
|
||||
input = result
|
||||
elseif io.type(file) then
|
||||
input = file
|
||||
else
|
||||
error("bad argument #1 (string or file expected, got " .. type(file) .. ")")
|
||||
end
|
||||
end
|
||||
return input
|
||||
end
|
||||
|
||||
function io.open(...)
|
||||
local result, reason = driver.fs.open(...)
|
||||
if result then
|
||||
current = result
|
||||
end
|
||||
return result, reason
|
||||
end
|
||||
|
||||
function io.output(file)
|
||||
if file then
|
||||
if type(file) == "string" then
|
||||
local result, reason = io.open(file, "w")
|
||||
if not result then
|
||||
error(reason)
|
||||
end
|
||||
output = result
|
||||
elseif io.type(file) then
|
||||
output = file
|
||||
else
|
||||
error("bad argument #1 (string or file expected, got " .. type(file) .. ")")
|
||||
end
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
function io.read(...)
|
||||
if current then
|
||||
return current:read(...)
|
||||
end
|
||||
end
|
||||
|
||||
function io.write(...)
|
||||
if current then
|
||||
return current:write(...)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
event.listen("component_added", function(_, address)
|
||||
if component.type(address) == "filesystem" and address ~= os.romAddress() then
|
||||
local name = address:sub(1, 3)
|
||||
|
@ -1,19 +1,20 @@
|
||||
local gpu = nil
|
||||
local gpuAddress, screenAddress, keyboardAddress = false, false, false
|
||||
local screenWidth, screenHeight = 0, 0
|
||||
local gpuAddress, screenAddress, keyboardAddress = nil, nil, nil
|
||||
local width, height = 0, 0
|
||||
local cursorX, cursorY = 1, 1
|
||||
local cursorBlink = nil
|
||||
|
||||
local function bindIfPossible()
|
||||
if gpuAddress and screenAddress then
|
||||
if not gpu then
|
||||
gpu = driver.gpu.bind(gpuAddress, screenAddress)
|
||||
screenWidth, screenHeight = gpu.getResolution()
|
||||
event.fire("term_available")
|
||||
end
|
||||
elseif gpu then
|
||||
gpu = nil
|
||||
screenWidth, screenHeight = 0, 0
|
||||
local function rebind(gpu, screen)
|
||||
if gpu == gpuAddress and screen == screenAddress then
|
||||
return
|
||||
end
|
||||
local oldGpu, oldScreen = gpuAddress, screenAddress
|
||||
gpuAddress, screenAddress = gpu, screen
|
||||
if gpu and screen then
|
||||
driver.gpu.bind(gpuAddress, screenAddress)
|
||||
width, height = driver.gpu.resolution(gpuAddress)
|
||||
event.fire("term_available")
|
||||
elseif gpuAddress and screenAddress then
|
||||
width, height = 0, 0
|
||||
event.fire("term_unavailable")
|
||||
end
|
||||
end
|
||||
@ -22,35 +23,26 @@ end
|
||||
|
||||
term = {}
|
||||
|
||||
function term.gpu(address)
|
||||
if address ~= nil and ({boolean=true, string=true})[type(address)] then
|
||||
gpuAddress = address
|
||||
bindIfPossible()
|
||||
end
|
||||
return gpuAddress, gpu
|
||||
function term.available()
|
||||
return gpuAddress and screenAddress
|
||||
end
|
||||
|
||||
function term.screen(address)
|
||||
if address ~= nil and ({boolean=true, string=true})[type(address)] then
|
||||
screenAddress = address
|
||||
bindIfPossible()
|
||||
function term.clear()
|
||||
if term.available() then
|
||||
driver.gpu.fill(term.gpu(), 1, 1, width, height, " ")
|
||||
end
|
||||
return screenAddress
|
||||
cursorX, cursorY = 1, 1
|
||||
end
|
||||
|
||||
function term.keyboard(address)
|
||||
if address ~= nil and ({boolean=true, string=true})[type(address)] then
|
||||
keyboardAddress = address
|
||||
function term.clearLine()
|
||||
if term.available() then
|
||||
driver.gpu.fill(term.gpu(), 1, cursorY, width, 1, " ")
|
||||
end
|
||||
return keyboardAddress
|
||||
end
|
||||
|
||||
function term.screenSize()
|
||||
return screenWidth, screenHeight
|
||||
cursorX = 1
|
||||
end
|
||||
|
||||
function term.cursor(col, row)
|
||||
if row and col then
|
||||
if col and row then
|
||||
cursorX = math.max(col, 1)
|
||||
cursorY = math.max(row, 1)
|
||||
end
|
||||
@ -61,12 +53,10 @@ function term.cursorBlink(enabled)
|
||||
if type(enabled) == "boolean" and enabled ~= (cursorBlink ~= nil) then
|
||||
local function toggleBlink()
|
||||
cursorBlink.state = not cursorBlink.state
|
||||
if gpu then
|
||||
if cursorBlink.state then
|
||||
gpu.set(cursorX, cursorY, string.char(0x2588)) -- Solid block.
|
||||
else
|
||||
gpu.set(cursorX, cursorY, " ")
|
||||
end
|
||||
if term.available() then
|
||||
-- 0x2588 is a solid block.
|
||||
local char = cursorBlink.state and string.char(0x2588) or " "
|
||||
driver.gpu.set(term.gpu(), cursorX, cursorY, char)
|
||||
end
|
||||
end
|
||||
if enabled then
|
||||
@ -83,20 +73,52 @@ function term.cursorBlink(enabled)
|
||||
return cursorBlink ~= nil
|
||||
end
|
||||
|
||||
function term.gpu(...)
|
||||
local args = table.pack(...)
|
||||
if args.n > 0 then
|
||||
checkArg(1, args[1], "string", "nil")
|
||||
rebind(args[1], term.screen())
|
||||
end
|
||||
return gpuAddress
|
||||
end
|
||||
|
||||
function term.keyboard(...)
|
||||
local args = table.pack(...)
|
||||
if args.n > 0 then
|
||||
checkArg(1, args[1], "string", "nil")
|
||||
keyboardAddress = args[1]
|
||||
end
|
||||
return keyboardAddress
|
||||
end
|
||||
|
||||
function term.screen(...)
|
||||
local args = table.pack(...)
|
||||
if args.n > 0 then
|
||||
checkArg(1, args[1], "string", "nil")
|
||||
rebind(term.gpu(), args[1])
|
||||
end
|
||||
return screenAddress
|
||||
end
|
||||
|
||||
function term.size()
|
||||
return width, height
|
||||
end
|
||||
|
||||
function term.write(value, wrap)
|
||||
value = tostring(value)
|
||||
local w, h = screenWidth, screenHeight
|
||||
if value:len() == 0 or not gpu or w < 1 or h < 1 then
|
||||
local w, h = width, height
|
||||
if value:len() == 0 or not term.available() or w < 1 or h < 1 then
|
||||
return
|
||||
end
|
||||
value = value:gsub("\t", " ")
|
||||
local function checkCursor()
|
||||
if cursorX > w then
|
||||
cursorX = 1
|
||||
cursorY = cursorY + 1
|
||||
end
|
||||
if cursorY > h then
|
||||
gpu.copy(1, 1, w, h, 0, -1)
|
||||
gpu.fill(1, h, w, 1, " ")
|
||||
driver.gpu.copy(term.gpu(), 1, 1, w, h, 0, -1)
|
||||
driver.gpu.fill(term.gpu(), 1, h, w, 1, " ")
|
||||
cursorY = h
|
||||
end
|
||||
end
|
||||
@ -104,12 +126,12 @@ function term.write(value, wrap)
|
||||
while wrap and line:len() > w - cursorX + 1 do
|
||||
local partial = line:sub(1, w - cursorX + 1)
|
||||
line = line:sub(partial:len() + 1)
|
||||
gpu.set(cursorX, cursorY, partial)
|
||||
driver.gpu.set(term.gpu(), cursorX, cursorY, partial)
|
||||
cursorX = cursorX + partial:len()
|
||||
checkCursor()
|
||||
end
|
||||
if line:len() > 0 then
|
||||
gpu.set(cursorX, cursorY, line)
|
||||
driver.gpu.set(term.gpu(), cursorX, cursorY, line)
|
||||
cursorX = cursorX + line:len()
|
||||
end
|
||||
if nl:len() == 1 then
|
||||
@ -120,62 +142,38 @@ function term.write(value, wrap)
|
||||
end
|
||||
end
|
||||
|
||||
function term.clear()
|
||||
if not gpu then return end
|
||||
gpu.fill(1, 1, screenWidth, screenHeight, " ")
|
||||
cursorX, cursorY = 1, 1
|
||||
end
|
||||
|
||||
function term.clearLine()
|
||||
if not gpu then return end
|
||||
gpu.fill(1, cursorY, screenWidth, 1, " ")
|
||||
cursorX = 1
|
||||
end
|
||||
|
||||
write = function(...)
|
||||
local args = {...}
|
||||
local first = true
|
||||
for i = 1, #args do
|
||||
if not first then
|
||||
term.write(", ")
|
||||
end
|
||||
first = false
|
||||
term.write(args[i], true)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
event.listen("component_added", function(_, address)
|
||||
local type = component.type(address)
|
||||
if type == "gpu" and not gpuAddress then
|
||||
if type == "gpu" and not term.gpu() then
|
||||
term.gpu(address)
|
||||
elseif type == "screen" and not screenAddress then
|
||||
elseif type == "screen" and not term.screen() then
|
||||
term.screen(address)
|
||||
elseif type == "keyboard" and not keyboardAddress then
|
||||
elseif type == "keyboard" and not term.keyboard() then
|
||||
term.keyboard(address)
|
||||
end
|
||||
end)
|
||||
|
||||
event.listen("component_removed", function(_, address)
|
||||
if gpuAddress == address then
|
||||
term.gpu(false)
|
||||
if term.gpu() == address then
|
||||
term.gpu(nil)
|
||||
for address in component.list() do
|
||||
if component.type(address) == "gpu" then
|
||||
term.gpu(address)
|
||||
return
|
||||
end
|
||||
end
|
||||
elseif screenAddress == address then
|
||||
term.screen(false)
|
||||
elseif term.screen() == address then
|
||||
term.screen(nil)
|
||||
for address in component.list() do
|
||||
if component.type(address) == "screen" then
|
||||
term.screen(address)
|
||||
return
|
||||
end
|
||||
end
|
||||
elseif keyboardAddress == address then
|
||||
term.keyboard(false)
|
||||
elseif term.keyboard() == address then
|
||||
term.keyboard(nil)
|
||||
for address in component.list() do
|
||||
if component.type(address) == "keyboard" then
|
||||
term.keyboard(address)
|
||||
@ -186,8 +184,8 @@ event.listen("component_removed", function(_, address)
|
||||
end)
|
||||
|
||||
event.listen("screen_resized", function(_, address, w, h)
|
||||
if address == screenAddress then
|
||||
screenWidth = w
|
||||
screenHeight = h
|
||||
if term.screen() == address then
|
||||
width = w
|
||||
height = h
|
||||
end
|
||||
end)
|
21
assets/opencomputers/lua/rom/bin/automount.lua
Normal file
21
assets/opencomputers/lua/rom/bin/automount.lua
Normal file
@ -0,0 +1,21 @@
|
||||
event.listen("component_added", function(_, address)
|
||||
if component.type(address) == "filesystem" and address ~= os.romAddress() then
|
||||
local name = address:sub(1, 3)
|
||||
repeat
|
||||
name = address:sub(1, name:len() + 1)
|
||||
until not driver.fs.exists("/mnt/" .. name)
|
||||
driver.fs.mount(address, "/mnt/" .. name)
|
||||
local autorun = "/mnt/" .. name .. "/autorun"
|
||||
if driver.fs.exists(autorun .. ".lua") then
|
||||
dofile(autorun .. ".lua")
|
||||
elseif driver.fs.exists(autorun) then
|
||||
dofile(autorun)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
event.listen("component_removed", function(_, address)
|
||||
if component.type(address) == "filesystem" then
|
||||
driver.fs.umount(address)
|
||||
end
|
||||
end)
|
@ -6,15 +6,15 @@ local isRunning = false
|
||||
local function onKeyDown(_, address, char, code)
|
||||
if isRunning then return end -- ignore events while running a command
|
||||
if address ~= term.keyboard() then return end
|
||||
local _, gpu = term.gpu()
|
||||
if not gpu then return end
|
||||
if not term.available() then return end
|
||||
local x, y = term.cursor()
|
||||
local w, h = term.size()
|
||||
local keys = driver.keyboard.keys
|
||||
if code == keys.back then
|
||||
if command:len() == 0 then return end
|
||||
command = command:sub(1, -2)
|
||||
term.cursor(command:len() + 3, y) -- from leading "> "
|
||||
gpu.set(x - 1, y, " ") -- overwrite cursor blink
|
||||
driver.gpu.set(term.gpu(), x - 1, y, " ") -- overwrite cursor blink
|
||||
elseif code == keys.enter then
|
||||
if command:len() == 0 then return end
|
||||
term.cursorBlink(false)
|
||||
@ -25,21 +25,21 @@ local function onKeyDown(_, address, char, code)
|
||||
end
|
||||
if code then
|
||||
isRunning = true
|
||||
local result = {pcall(code)}
|
||||
local result = table.pack(pcall(code))
|
||||
isRunning = false
|
||||
if not result[1] or #result > 1 then
|
||||
print(table.unpack(result, 2))
|
||||
if not result[1] or result.n > 1 then
|
||||
print(table.unpack(result, 2, result.n))
|
||||
end
|
||||
else
|
||||
print(result)
|
||||
end
|
||||
lastCommand = command
|
||||
command = ""
|
||||
write("> ")
|
||||
term.write("> ")
|
||||
term.cursorBlink(true)
|
||||
elseif code == keys.up then
|
||||
command = lastCommand
|
||||
gpu.fill(3, y, screenWidth, 1, " ")
|
||||
driver.gpu.fill(term.gpu(), 3, y, w, 1, " ")
|
||||
term.cursor(3, y)
|
||||
term.write(command)
|
||||
term.cursor(command:len() + 3, y)
|
||||
@ -64,7 +64,7 @@ event.listen("term_available", function()
|
||||
term.clear()
|
||||
command = ""
|
||||
print("OpenOS v1.0 (" .. math.floor(os.totalMemory() / 1024) .. "k RAM)")
|
||||
write("> ")
|
||||
term.write("> ")
|
||||
event.listen("key_down", onKeyDown)
|
||||
event.listen("clipboard", onClipboard)
|
||||
end)
|
@ -1,12 +1,12 @@
|
||||
dofile("/boot/api/event.lua")
|
||||
dofile("/boot/api/component.lua")
|
||||
dofile("/boot/api/filesystem.lua")
|
||||
dofile("/boot/api/term.lua")
|
||||
dofile("/boot/sh.lua")
|
||||
dofile("/boot/bin/automount.lua")
|
||||
dofile("/boot/bin/sh.lua")
|
||||
|
||||
driver.fs.umount("/boot")
|
||||
|
||||
event.fire(...)
|
||||
while true do
|
||||
event.fire(os.signal())
|
||||
coroutine.sleep()
|
||||
end
|
||||
|
@ -9,7 +9,7 @@ object Blocks {
|
||||
var screen: Screen = null
|
||||
var keyboard: Keyboard = null
|
||||
var powersupply: PowerSupply = null
|
||||
var powerdistributer: PowerDistributor = null
|
||||
var powerdistributor: PowerDistributor = null
|
||||
def init() {
|
||||
// IMPORTANT: the multi block must come first, since the sub blocks will
|
||||
// try to register with it. Also, the order the sub blocks are created in
|
||||
@ -21,6 +21,6 @@ object Blocks {
|
||||
screen = new Screen(blockSimple)
|
||||
keyboard = new Keyboard(blockSpecial)
|
||||
powersupply = new PowerSupply(blockSimple)
|
||||
powerdistributer = new PowerDistributor(blockSimple)
|
||||
powerdistributor = new PowerDistributor(blockSimple)
|
||||
}
|
||||
}
|
@ -16,8 +16,33 @@ import li.cil.oc.api.network.Node
|
||||
* <p/>
|
||||
* Alternatively to using the factory methods for file systems in `Filesystem`
|
||||
* you are free to implement this interface yourself.
|
||||
* <p/>
|
||||
* Note that all paths passed here are assumed to be absolute in the underlying
|
||||
* file system implementation, meaning they do not contain any "." or "..", and
|
||||
* are relative to the root of the file system.
|
||||
*/
|
||||
trait FileSystem extends Persistable {
|
||||
/**
|
||||
* The total storage capacity of the file system, in bytes.
|
||||
* <p/>
|
||||
* For read-only systems this should return zero, for writable file systems
|
||||
* that do not enforce a storage limit this should be a negative value.
|
||||
*
|
||||
* @return the total storage space of this file system.
|
||||
*/
|
||||
def spaceTotal = 0L
|
||||
|
||||
/**
|
||||
* The used storage capacity of the file system, in bytes.
|
||||
* <p/>
|
||||
* For read-only systems this should return zero.
|
||||
*
|
||||
* @return the used storage space of this file system.
|
||||
*/
|
||||
def spaceUsed = 0L
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
/**
|
||||
* Tests if a file or directory exists at the specified path.
|
||||
* <p/>
|
||||
@ -75,6 +100,20 @@ trait FileSystem extends Persistable {
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
/**
|
||||
* Create the specified directory, and if necessary any parent directories
|
||||
* that do not yet exist.
|
||||
* <p/>
|
||||
* This is only available for writable file systems. For read-only systems
|
||||
* it should just always return false.
|
||||
*
|
||||
* @param path the path to the directory to create.
|
||||
* @return true if the directory was created; false otherwise.
|
||||
*/
|
||||
def makeDirectories(path: String): Boolean =
|
||||
!exists(path) && (makeDirectory(path) ||
|
||||
(makeDirectories(path.split("/").dropRight(1).mkString("/")) && makeDirectory(path)))
|
||||
|
||||
/**
|
||||
* Deletes a file or folder.
|
||||
* <p/>
|
||||
@ -153,6 +192,23 @@ trait FileSystem extends Persistable {
|
||||
*/
|
||||
def close()
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
/**
|
||||
* Actual directory creation implementation.
|
||||
* <p/>
|
||||
* This is called from the possibly recursive `makeDirectories` function, and
|
||||
* guarantees that the parent directory of the directory to be created
|
||||
* already exists. So this should always only create a single directory.
|
||||
* <p/>
|
||||
* This is only available for writable file systems. For read-only systems
|
||||
* it should just always return false.
|
||||
*
|
||||
* @param path the path to the directory to create.
|
||||
* @return true if the directory was created; false otherwise.
|
||||
*/
|
||||
protected def makeDirectory(path: String) = false
|
||||
|
||||
/**
|
||||
* Actual deletion implementation.
|
||||
* <p/>
|
||||
|
@ -70,16 +70,22 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private var timeStarted = 0L // Game-world time for os.clock().
|
||||
private var timeStarted = 0L // Game-world time [ms] for os.uptime().
|
||||
|
||||
private var worldTime = 0L // Game-world time for os.time().
|
||||
|
||||
private var lastUpdate = 0L // Real-world time for pause detection.
|
||||
private var lastUpdate = 0L // Real-world time [ms] for pause detection.
|
||||
|
||||
private var sleepUntil = Long.MaxValue // Real-world time.
|
||||
private var cpuTime = 0L // Pseudo-real-world time [ns] for os.clock().
|
||||
|
||||
private var cpuStart = 0L // Pseudo-real-world time [ns] for os.clock().
|
||||
|
||||
private var sleepUntil = Double.PositiveInfinity // Real-world time [ms].
|
||||
|
||||
private var wasRunning = false // To signal stops synchronously.
|
||||
|
||||
private var message: Option[String] = None // For error messages.
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
def recomputeMemory() = if (lua != null)
|
||||
@ -154,10 +160,25 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
worldTime = owner.world.getWorldInfo.getWorldTotalTime
|
||||
|
||||
// Signal stops to the network. This is used to close file handles, for example.
|
||||
if (wasRunning && !isRunning)
|
||||
if (wasRunning && !isRunning) {
|
||||
owner.network.foreach(_.sendToVisible(owner, "computer.stopped"))
|
||||
// Clear any screens we use while we're at it.
|
||||
owner.network.foreach(_.sendToNeighbors(owner, "gpu.fill",
|
||||
1.0, 1.0, Double.PositiveInfinity, Double.PositiveInfinity, " ".getBytes("UTF-8")))
|
||||
}
|
||||
wasRunning = isRunning
|
||||
|
||||
// If there was an error message (i.e. the computer crashed) display it on
|
||||
// any screens we used (stored in GPUs).
|
||||
if (message.isDefined) {
|
||||
owner.network.foreach(network => {
|
||||
for ((line, row) <- message.get.lines.zipWithIndex) {
|
||||
network.sendToNeighbors(owner, "gpu.set", 1.0, 1.0 + row, line.getBytes("UTF-8"))
|
||||
}
|
||||
})
|
||||
message = None
|
||||
}
|
||||
|
||||
// Check if we should switch states.
|
||||
stateMonitor.synchronized(state match {
|
||||
// Computer is rebooting.
|
||||
@ -204,16 +225,14 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
// This can happen if we run out of memory while converting a Java
|
||||
// exception to a string (which we have to do to avoid keeping
|
||||
// userdata on the stack, which cannot be persisted).
|
||||
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages
|
||||
//owner.network.foreach(_.sendToVisible(owner, "computer.crashed", "not enough memory"))
|
||||
message = Some("not enough memory")
|
||||
close()
|
||||
case e: java.lang.Error if e.getMessage == "not enough memory" =>
|
||||
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages
|
||||
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
|
||||
//owner.network.foreach(_.sendToVisible(owner, "computer.crashed", "not enough memory"))
|
||||
message = Some("not enough memory")
|
||||
close()
|
||||
case e: Throwable => {
|
||||
OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e)
|
||||
message = Some("protocol error")
|
||||
close()
|
||||
}
|
||||
}
|
||||
@ -273,6 +292,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
rom.foreach(_.load(nbt.getCompoundTag("rom")))
|
||||
kernelMemory = nbt.getInteger("kernelMemory")
|
||||
timeStarted = nbt.getLong("timeStarted")
|
||||
cpuTime = nbt.getLong("cpuTime")
|
||||
if (nbt.hasKey("message"))
|
||||
message = Some(nbt.getString("message"))
|
||||
|
||||
// Clean up some after we're done and limit memory again.
|
||||
lua.gc(LuaState.GcAction.COLLECT, 0)
|
||||
@ -353,6 +375,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
nbt.setCompoundTag("rom", romNbt)
|
||||
nbt.setInteger("kernelMemory", kernelMemory)
|
||||
nbt.setLong("timeStarted", timeStarted)
|
||||
nbt.setLong("cpuTime", cpuTime)
|
||||
if (message.isDefined)
|
||||
nbt.setString("message", message.get)
|
||||
}
|
||||
catch {
|
||||
case e: Throwable => {
|
||||
@ -417,12 +442,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
lua.getGlobal("os")
|
||||
|
||||
// Custom os.clock() implementation returning the time the computer has
|
||||
// been running, instead of the native library...
|
||||
// been actively running, instead of the native library...
|
||||
lua.pushScalaFunction(lua => {
|
||||
// World time is in ticks, and each second has 20 ticks. Since we
|
||||
// want os.clock() to return real seconds, though, we'll divide it
|
||||
// accordingly.
|
||||
lua.pushNumber((worldTime - timeStarted) / 20.0)
|
||||
lua.pushNumber((cpuTime + (System.nanoTime() - cpuStart)) * 10e-10)
|
||||
1
|
||||
})
|
||||
lua.setField(-2, "clock")
|
||||
@ -438,6 +460,16 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
})
|
||||
lua.setField(-2, "time")
|
||||
|
||||
// The time the computer has been running, as opposed to the CPU time.
|
||||
lua.pushScalaFunction(lua => {
|
||||
// World time is in ticks, and each second has 20 ticks. Since we
|
||||
// want os.uptime() to return real seconds, though, we'll divide it
|
||||
// accordingly.
|
||||
lua.pushNumber((worldTime - timeStarted) / 20.0)
|
||||
1
|
||||
})
|
||||
lua.setField(-2, "uptime")
|
||||
|
||||
// Allow the system to read how much memory it uses and has available.
|
||||
lua.pushScalaFunction(lua => {
|
||||
lua.pushInteger(lua.getTotalMemory - kernelMemory)
|
||||
@ -541,7 +573,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
results.length
|
||||
case _ => 0
|
||||
})
|
||||
lua.setGlobal("sendToNode")
|
||||
lua.setGlobal("sendToAddress")
|
||||
|
||||
lua.pushScalaFunction(lua => {
|
||||
owner.network.fold(None: Option[Node])(_.node(lua.checkString(1))) match {
|
||||
@ -592,7 +624,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
// underlying system (which may change across releases). Add some buffer
|
||||
// to avoid the init script eating up all the rest immediately.
|
||||
lua.gc(LuaState.GcAction.COLLECT, 0)
|
||||
kernelMemory = (lua.getTotalMemory - lua.getFreeMemory) + 2048
|
||||
kernelMemory = (lua.getTotalMemory - lua.getFreeMemory) + 8 * 1024
|
||||
recomputeMemory()
|
||||
|
||||
// Clear any left-over signals from a previous run.
|
||||
@ -621,6 +653,8 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
kernelMemory = 0
|
||||
signals.clear()
|
||||
timeStarted = 0
|
||||
cpuTime = 0
|
||||
cpuStart = 0
|
||||
future = None
|
||||
sleepUntil = Long.MaxValue
|
||||
|
||||
@ -628,6 +662,8 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
owner.markAsChanged()
|
||||
})
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private def execute(value: Computer.State.Value) {
|
||||
assert(future.isEmpty)
|
||||
sleepUntil = Long.MaxValue
|
||||
@ -666,6 +702,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
|
||||
try {
|
||||
// Resume the Lua state and remember the number of results we get.
|
||||
cpuStart = System.nanoTime()
|
||||
val results = if (callReturn) {
|
||||
// If we were doing a synchronized call, continue where we left off.
|
||||
assert(lua.getTop == 2)
|
||||
@ -685,6 +722,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
lua.resume(1, 1 + signal.args.length)
|
||||
}
|
||||
}
|
||||
cpuTime += System.nanoTime() - cpuStart
|
||||
|
||||
// Check if the kernel is still alive.
|
||||
stateMonitor.synchronized(if (lua.status(1) == LuaState.YIELD) {
|
||||
@ -696,7 +734,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
// If we have a single number that's how long we may wait before
|
||||
// resuming the state again.
|
||||
else if (results == 1 && lua.isNumber(2)) {
|
||||
val sleep = (lua.toNumber(2) * 1000).toLong
|
||||
val sleep = lua.toNumber(2) * 1000
|
||||
lua.pop(results)
|
||||
// But only sleep if we don't have more signals to process.
|
||||
if (signals.isEmpty) {
|
||||
@ -746,9 +784,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
else {
|
||||
// This can trigger another out of memory error if the original
|
||||
// error was an out of memory error.
|
||||
OpenComputers.log.warning("Computer crashed.\n" + lua.toString(3)) // TODO remove this when we have a component that can display crash messages
|
||||
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
|
||||
//owner.network.sendToAll(owner, "computer.crashed", lua.toString(3))
|
||||
message = Some(lua.toString(3))
|
||||
}
|
||||
close()
|
||||
})
|
||||
@ -756,16 +792,13 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
catch {
|
||||
case e: LuaRuntimeException =>
|
||||
OpenComputers.log.warning("Kernel crashed. This is a bug!\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat "))
|
||||
message = Some("kernel panic")
|
||||
close()
|
||||
case e: LuaMemoryAllocationException =>
|
||||
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages
|
||||
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
|
||||
//owner.network.sendToAll(owner, "computer.crashed", "not enough memory")
|
||||
message = Some("not enough memory")
|
||||
close()
|
||||
case e: java.lang.Error if e.getMessage == "not enough memory" =>
|
||||
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages
|
||||
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
|
||||
//owner.network.sendToAll(owner, "computer.crashed", "not enough memory")
|
||||
message = Some("not enough memory")
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,14 @@ class FileSystem(val fileSystem: api.FileSystem) extends Node {
|
||||
set.clear()
|
||||
}
|
||||
None
|
||||
case Array() if message.name == "fs.spaceTotal" =>
|
||||
val space = fileSystem.spaceTotal
|
||||
if (space < 0)
|
||||
Some(Array("unlimited"))
|
||||
else
|
||||
Some(Array(space.asInstanceOf[Any]))
|
||||
case Array() if message.name == "fs.spaceUsed" =>
|
||||
Some(Array(fileSystem.spaceUsed.asInstanceOf[Any]))
|
||||
case Array(path: Array[Byte]) if message.name == "fs.exists" =>
|
||||
Some(Array(fileSystem.exists(clean(path)).asInstanceOf[Any]))
|
||||
case Array(path: Array[Byte]) if message.name == "fs.size" =>
|
||||
|
@ -1,10 +1,16 @@
|
||||
package li.cil.oc.server.component
|
||||
|
||||
import li.cil.oc.api.network.{Node, Visibility, Message}
|
||||
import li.cil.oc.common.component.ScreenEnvironment
|
||||
import net.minecraft.nbt.NBTTagCompound
|
||||
|
||||
class GraphicsCard extends Node {
|
||||
val supportedResolutions = List(List(40, 24), List(80, 24))
|
||||
|
||||
private var screen: Option[String] = None
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def name = "gpu"
|
||||
|
||||
override def visibility = Visibility.Neighbors
|
||||
@ -12,32 +18,66 @@ class GraphicsCard extends Node {
|
||||
override def receive(message: Message) = {
|
||||
super.receive(message)
|
||||
message.data match {
|
||||
case Array(screen: Array[Byte], w: Double, h: Double) if message.name == "gpu.resolution=" =>
|
||||
case Array(address: Array[Byte]) if message.name == "gpu.bind" =>
|
||||
network.fold(None: Option[Array[Any]])(network => {
|
||||
network.node(new String(address, "UTF-8")) match {
|
||||
case None => Some(Array(Unit, "invalid address"))
|
||||
case Some(node: ScreenEnvironment) =>
|
||||
screen = node.address
|
||||
Some(Array(true.asInstanceOf[Any]))
|
||||
case _ => Some(Array(Unit, "not a screen"))
|
||||
}
|
||||
})
|
||||
case Array() if message.name == "network.disconnect" && message.source.address == screen => screen = None; None
|
||||
case Array(w: Double, h: Double) if message.name == "gpu.resolution=" =>
|
||||
if (supportedResolutions.contains((w.toInt, h.toInt)))
|
||||
trySend(new String(screen, "UTF-8"), "screen.resolution=", w.toInt, h.toInt)
|
||||
trySend("screen.resolution=", w.toInt, h.toInt)
|
||||
else
|
||||
Some(Array(Unit, "unsupported resolution"))
|
||||
case Array(screen: Array[Byte]) if message.name == "gpu.resolution" =>
|
||||
trySend(new String(screen, "UTF-8"), "screen.resolution")
|
||||
case Array(screen: Array[Byte]) if message.name == "gpu.resolutions" =>
|
||||
trySend(new String(screen, "UTF-8"), "screen.resolutions") match {
|
||||
case Some(Array(resolutions@_*)) =>
|
||||
Some(Array(supportedResolutions.intersect(resolutions): _*))
|
||||
case _ => None
|
||||
}
|
||||
case Array(screen: Array[Byte], x: Double, y: Double, value: Array[Byte]) if message.name == "gpu.set" =>
|
||||
trySend(new String(screen, "UTF-8"), "screen.set", x.toInt - 1, y.toInt - 1, new String(value, "UTF-8"))
|
||||
case Array(screen: Array[Byte], x: Double, y: Double, w: Double, h: Double, value: Array[Byte]) if message.name == "gpu.fill" =>
|
||||
case Array() if message.name == "gpu.resolution" => trySend("screen.resolution")
|
||||
case Array() if message.name == "gpu.resolutions" => trySend("screen.resolutions") match {
|
||||
case Some(Array(resolutions@_*)) =>
|
||||
Some(Array(supportedResolutions.intersect(resolutions): _*))
|
||||
case _ => None
|
||||
}
|
||||
case Array(x: Double, y: Double, value: Array[Byte]) if message.name == "gpu.set" =>
|
||||
trySend("screen.set", x.toInt - 1, y.toInt - 1, new String(value, "UTF-8"))
|
||||
case Array(x: Double, y: Double, w: Double, h: Double, value: Array[Byte]) if message.name == "gpu.fill" =>
|
||||
val s = new String(value, "UTF-8")
|
||||
if (s.length == 1)
|
||||
trySend(new String(screen, "UTF-8"), "screen.fill", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, s.charAt(0))
|
||||
trySend("screen.fill", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, s.charAt(0))
|
||||
else
|
||||
Some(Array(Unit, "invalid fill value"))
|
||||
case Array(screen: Array[Byte], x: Double, y: Double, w: Double, h: Double, tx: Double, ty: Double) if message.name == "gpu.copy" =>
|
||||
trySend(new String(screen, "UTF-8"), "screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt)
|
||||
case Array(x: Double, y: Double, w: Double, h: Double, tx: Double, ty: Double) if message.name == "gpu.copy" =>
|
||||
trySend("screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
private def trySend(target: String, name: String, data: Any*) = network.fold(None: Option[Array[Any]])(net => net.sendToAddress(this, target, name, data: _*))
|
||||
override protected def onDisconnect() = {
|
||||
super.onDisconnect()
|
||||
screen = None
|
||||
}
|
||||
|
||||
override def load(nbt: NBTTagCompound) = {
|
||||
super.load(nbt)
|
||||
if (nbt.hasKey("screen"))
|
||||
screen = Some(nbt.getString("screen"))
|
||||
}
|
||||
|
||||
override def save(nbt: NBTTagCompound) = {
|
||||
super.save(nbt)
|
||||
if (screen.isDefined)
|
||||
nbt.setString("screen", screen.get)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private def trySend(name: String, data: Any*): Option[Array[Any]] =
|
||||
screen match {
|
||||
case None => Some(Array(Unit, "no screen"))
|
||||
case Some(screenAddress) => network.fold(None: Option[Array[Any]])(net => {
|
||||
net.sendToAddress(this, screenAddress, name, data: _*)
|
||||
})
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ import li.cil.oc.{Config, Items}
|
||||
import net.minecraft.item.ItemStack
|
||||
|
||||
object FileSystem extends Item {
|
||||
override def api = Option(getClass.getResourceAsStream(Config.driverPath + "fs.lua"))
|
||||
override def api = Option(getClass.getResourceAsStream(Config.driverPath + "filesystem.lua"))
|
||||
|
||||
override def worksWith(item: ItemStack) = WorksWith(Items.hdd2, Items.hdd4, Items.hdd8)(item)
|
||||
|
||||
|
@ -7,7 +7,19 @@ import li.cil.oc.api.fs.Mode
|
||||
trait Capacity extends OutputStreamFileSystem {
|
||||
private var used = computeSize("/")
|
||||
|
||||
def capacity: Int
|
||||
protected def capacity: Int
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def spaceTotal = capacity
|
||||
|
||||
override def spaceUsed = used
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def makeDirectories(path: String) = {
|
||||
super.makeDirectories(path)
|
||||
}
|
||||
|
||||
override protected def delete(path: String) = {
|
||||
val freed = Config.fileCost + size(path)
|
||||
@ -18,6 +30,8 @@ trait Capacity extends OutputStreamFileSystem {
|
||||
else false
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override protected abstract def openOutputStream(path: String, mode: Mode.Value): Option[io.OutputStream] = {
|
||||
val delta =
|
||||
if (exists(path))
|
||||
@ -35,6 +49,8 @@ trait Capacity extends OutputStreamFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private def computeSize(path: String): Long =
|
||||
Config.fileCost +
|
||||
size(path) +
|
||||
|
@ -7,6 +7,8 @@ import li.cil.oc.api
|
||||
trait FileInputStreamFileSystem extends api.FileSystem with InputStreamFileSystem {
|
||||
protected val root: io.File
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def exists(path: String) = new io.File(root, path).exists()
|
||||
|
||||
override def size(path: String) = new io.File(root, path) match {
|
||||
@ -23,6 +25,8 @@ trait FileInputStreamFileSystem extends api.FileSystem with InputStreamFileSyste
|
||||
case _ => throw new FileNotFoundException("no such file or directory")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override protected def openInputStream(path: String) =
|
||||
Some(new FileInputStream(new io.File(root, path)))
|
||||
}
|
||||
|
@ -5,10 +5,22 @@ import java.io.FileOutputStream
|
||||
import li.cil.oc.api.fs.Mode
|
||||
|
||||
trait FileOutputStreamFileSystem extends FileInputStreamFileSystem with OutputStreamFileSystem {
|
||||
override protected def openOutputStream(path: String, mode: Mode.Value): Option[io.OutputStream] =
|
||||
Some(new FileOutputStream(new io.File(root, path), mode == Mode.Append))
|
||||
override def spaceTotal = -1
|
||||
|
||||
override def spaceUsed = root.getFreeSpace
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def rename(from: String, to: String) = new io.File(root, from).renameTo(new io.File(root, to))
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override protected def makeDirectory(path: String) = new io.File(root, path).mkdir()
|
||||
|
||||
override protected def delete(path: String) = new io.File(root, path).delete()
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override protected def openOutputStream(path: String, mode: Mode.Value): Option[io.OutputStream] =
|
||||
Some(new FileOutputStream(new io.File(root, path), mode == Mode.Append))
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ object FileSystem extends api.detail.FileSystemAPI {
|
||||
extends InputStreamFileSystem
|
||||
with FileInputStreamFileSystem
|
||||
|
||||
private class ReadWriteFileSystem(protected val root: io.File, val capacity: Int)
|
||||
private class ReadWriteFileSystem(protected val root: io.File, protected val capacity: Int)
|
||||
extends OutputStreamFileSystem
|
||||
with FileOutputStreamFileSystem
|
||||
with Capacity
|
||||
|
@ -9,6 +9,8 @@ import scala.collection.mutable
|
||||
trait InputStreamFileSystem extends api.FileSystem {
|
||||
private val handles = mutable.Map.empty[Int, Handle]
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def open(path: String, mode: Mode.Value) = if (mode == Mode.Read && exists(path) && !isDirectory(path)) {
|
||||
val handle = Iterator.continually((Math.random() * Int.MaxValue).toInt + 1).filterNot(handles.contains).next()
|
||||
openInputStream(path) match {
|
||||
@ -27,6 +29,8 @@ trait InputStreamFileSystem extends api.FileSystem {
|
||||
handles.clear()
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def load(nbt: NBTTagCompound) {
|
||||
val handlesNbt = nbt.getTagList("input")
|
||||
(0 until handlesNbt.tagCount).map(handlesNbt.tagAt).map(_.asInstanceOf[NBTTagCompound]).foreach(handleNbt => {
|
||||
@ -56,8 +60,12 @@ trait InputStreamFileSystem extends api.FileSystem {
|
||||
nbt.setTag("input", handlesNbt)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
protected def openInputStream(path: String): Option[InputStream]
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private class Handle(val owner: InputStreamFileSystem, val handle: Int, val path: String, val stream: InputStream) extends api.fs.Handle {
|
||||
var isClosed = false
|
||||
var position = 0L
|
||||
@ -82,7 +90,7 @@ trait InputStreamFileSystem extends api.FileSystem {
|
||||
stream.skip(to)
|
||||
}
|
||||
|
||||
def write(value: Array[Byte]) = throw new IOException("handle is read-only")
|
||||
def write(value: Array[Byte]) = throw new IOException("bad file descriptor")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import scala.collection.mutable
|
||||
trait OutputStreamFileSystem extends InputStreamFileSystem {
|
||||
private val handles = mutable.Map.empty[Int, Handle]
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def open(path: String, mode: Mode.Value) = mode match {
|
||||
case Mode.Read => super.open(path, mode)
|
||||
case _ => if (!isDirectory(path)) {
|
||||
@ -31,6 +33,8 @@ trait OutputStreamFileSystem extends InputStreamFileSystem {
|
||||
handles.clear()
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def load(nbt: NBTTagCompound) {
|
||||
super.load(nbt)
|
||||
val handlesNbt = nbt.getTagList("output")
|
||||
@ -59,8 +63,12 @@ trait OutputStreamFileSystem extends InputStreamFileSystem {
|
||||
nbt.setTag("output", handlesNbt)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
protected def openOutputStream(path: String, mode: Mode.Value): Option[OutputStream]
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private class Handle(val owner: OutputStreamFileSystem, val handle: Int, val path: String, val stream: OutputStream) extends api.fs.Handle {
|
||||
var isClosed = false
|
||||
val position = 0L
|
||||
@ -72,9 +80,9 @@ trait OutputStreamFileSystem extends InputStreamFileSystem {
|
||||
stream.close()
|
||||
}
|
||||
|
||||
def read(into: Array[Byte]) = throw new IOException("handle is write-only")
|
||||
def read(into: Array[Byte]) = throw new IOException("bad file descriptor")
|
||||
|
||||
def seek(to: Long) = throw new IOException("handle is write-only")
|
||||
def seek(to: Long) = throw new IOException("bad file descriptor")
|
||||
|
||||
def write(value: Array[Byte]) {
|
||||
stream.write(value)
|
||||
|
@ -26,6 +26,8 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
|
||||
addressedNodes.values.foreach(_.data.network = Some(this))
|
||||
unaddressedNodes.foreach(_.data.network = Some(this))
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def connect(nodeA: api.network.Node, nodeB: api.network.Node) = {
|
||||
val containsA = contains(nodeA)
|
||||
val containsB = contains(nodeB)
|
||||
@ -104,6 +106,8 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def node(address: String) = addressedNodes.get(address) match {
|
||||
case Some(node) => Some(node.data)
|
||||
case _ => None
|
||||
@ -130,6 +134,8 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def sendToAddress(source: api.network.Node, target: String, name: String, data: Any*) = {
|
||||
if (source.network.isEmpty || source.network.get != this)
|
||||
throw new IllegalArgumentException("Source node must be in this network.")
|
||||
@ -157,6 +163,8 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
|
||||
else None
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private def contains(node: api.network.Node) = (node.address match {
|
||||
case None => unaddressedNodes.find(_.data == node)
|
||||
case Some(address) => addressedNodes.get(address)
|
||||
@ -306,6 +314,18 @@ object Network extends api.detail.NetworkAPI {
|
||||
}
|
||||
}
|
||||
|
||||
private def getNetworkNode(world: IBlockAccess, x: Int, y: Int, z: Int): Option[TileEntity with api.network.Node] =
|
||||
Option(Block.blocksList(world.getBlockId(x, y, z))) match {
|
||||
case Some(block) if block.hasTileEntity(world.getBlockMetadata(x, y, z)) =>
|
||||
world.getBlockTileEntity(x, y, z) match {
|
||||
case tileEntity: TileEntity with api.network.Node => Some(tileEntity)
|
||||
case _ => None
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
@ForgeSubscribe
|
||||
def onChunkUnload(e: ChunkEvent.Unload) =
|
||||
onUnload(e.world, e.getChunk.chunkTileEntityMap.values.asScala.map(_.asInstanceOf[TileEntity]))
|
||||
@ -326,15 +346,7 @@ object Network extends api.detail.NetworkAPI {
|
||||
tileEntities.foreach(t => joinOrCreateNetwork(w, t.xCoord, t.yCoord, t.zCoord))
|
||||
}
|
||||
|
||||
private def getNetworkNode(world: IBlockAccess, x: Int, y: Int, z: Int): Option[TileEntity with api.network.Node] =
|
||||
Option(Block.blocksList(world.getBlockId(x, y, z))) match {
|
||||
case Some(block) if block.hasTileEntity(world.getBlockMetadata(x, y, z)) =>
|
||||
world.getBlockTileEntity(x, y, z) match {
|
||||
case tileEntity: TileEntity with api.network.Node => Some(tileEntity)
|
||||
case _ => None
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private class Node(val data: api.network.Node) {
|
||||
val edges = ArrayBuffer.empty[Edge]
|
||||
@ -382,6 +394,8 @@ object Network extends api.detail.NetworkAPI {
|
||||
}) filter (_.nonEmpty) map (_.get)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private class Message(@BeanProperty val source: api.network.Node,
|
||||
@BeanProperty val name: String,
|
||||
@BeanProperty val data: Array[Any] = Array()) extends api.network.Message {
|
||||
|
@ -197,6 +197,14 @@ object LuaStateFactory {
|
||||
// Provide some better Unicode support.
|
||||
state.getGlobal("string")
|
||||
|
||||
// Rename stuff for binary functionality, to allow byte-wise operations
|
||||
// operations on the string.
|
||||
state.getField(-1, "sub")
|
||||
state.setField(-2, "bsub")
|
||||
|
||||
state.getField(-1, "reverse")
|
||||
state.setField(-2, "breverse")
|
||||
|
||||
state.pushScalaFunction(lua => {
|
||||
lua.pushString(String.valueOf((1 to lua.getTop).map(lua.checkInteger).map(_.toChar).toArray))
|
||||
1
|
||||
@ -231,11 +239,6 @@ object LuaStateFactory {
|
||||
})
|
||||
state.setField(-2, "reverse")
|
||||
|
||||
// Rename string.sub to string.bsub (for binary sub, to allow byte-wise
|
||||
// operations on the string).
|
||||
state.getField(-1, "sub")
|
||||
state.setField(-2, "bsub")
|
||||
|
||||
state.pushScalaFunction(lua => {
|
||||
val string = lua.checkString(1)
|
||||
val start = (lua.checkInteger(2) match {
|
||||
|
Loading…
x
Reference in New Issue
Block a user