mirror of
https://github.com/kepler155c/opus
synced 2025-01-16 10:25:42 +00:00
spaces->tabs + cleanup + pathing fixes
This commit is contained in:
parent
82ec4db50f
commit
3c22a872b0
@ -5,29 +5,29 @@ local parallel = _G.parallel
|
||||
local BulkGet = { }
|
||||
|
||||
function BulkGet.download(list, callback)
|
||||
local t = { }
|
||||
local failed = false
|
||||
local t = { }
|
||||
local failed = false
|
||||
|
||||
for _ = 1, 5 do
|
||||
table.insert(t, function()
|
||||
while true do
|
||||
local entry = table.remove(list)
|
||||
if not entry then
|
||||
break
|
||||
end
|
||||
local s, m = Util.download(entry.url, entry.path)
|
||||
if not s then
|
||||
failed = true
|
||||
end
|
||||
callback(entry, s, m)
|
||||
if failed then
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
for _ = 1, 5 do
|
||||
table.insert(t, function()
|
||||
while true do
|
||||
local entry = table.remove(list)
|
||||
if not entry then
|
||||
break
|
||||
end
|
||||
local s, m = Util.download(entry.url, entry.path)
|
||||
if not s then
|
||||
failed = true
|
||||
end
|
||||
callback(entry, s, m)
|
||||
if failed then
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
parallel.waitForAll(table.unpack(t))
|
||||
parallel.waitForAll(table.unpack(t))
|
||||
end
|
||||
|
||||
return BulkGet
|
||||
|
@ -1,61 +0,0 @@
|
||||
--[[
|
||||
Mount a readonly file system from another computer across rednet. The
|
||||
target computer must be running OpusOS or redserver. Dissimlar to samba
|
||||
in that a snapshot of the target is taken upon mounting - making this
|
||||
faster.
|
||||
|
||||
Useful for mounting a non-changing directory tree.
|
||||
|
||||
Syntax:
|
||||
rttp://<id>/directory/subdir
|
||||
|
||||
Examples:
|
||||
rttp://12/usr/etc
|
||||
rttp://8/usr
|
||||
]]--
|
||||
|
||||
local rttp = require('rttp')
|
||||
|
||||
local fs = _G.fs
|
||||
|
||||
local redfs = { }
|
||||
|
||||
local function getListing(uri)
|
||||
local success, response = rttp.get(uri .. '?recursive=true')
|
||||
|
||||
if not success then
|
||||
error(response)
|
||||
end
|
||||
|
||||
if response.statusCode ~= 200 then
|
||||
error('Received response ' .. response.statusCode)
|
||||
end
|
||||
|
||||
local list = { }
|
||||
for _,v in pairs(response.data) do
|
||||
if not v.isDir then
|
||||
list[v.path] = {
|
||||
url = uri .. '/' .. v.path,
|
||||
size = v.size,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
function redfs.mount(dir, uri)
|
||||
if not uri then
|
||||
error('redfs syntax: uri')
|
||||
end
|
||||
|
||||
local list = getListing(uri)
|
||||
for path, entry in pairs(list) do
|
||||
if not fs.exists(fs.combine(dir, path)) then
|
||||
local node = fs.mount(fs.combine(dir, path), 'urlfs', entry.url)
|
||||
node.size = entry.size
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return redfs
|
@ -1,4 +1,4 @@
|
||||
local rttp = require('rttp')
|
||||
--local rttp = require('rttp')
|
||||
local Util = require('util')
|
||||
|
||||
local fs = _G.fs
|
||||
@ -39,7 +39,6 @@ function urlfs.getDrive()
|
||||
end
|
||||
|
||||
function urlfs.open(node, fn, fl)
|
||||
|
||||
if fl == 'w' or fl == 'wb' then
|
||||
fs.delete(fn)
|
||||
return fs.open(fn, fl)
|
||||
@ -51,12 +50,15 @@ function urlfs.open(node, fn, fl)
|
||||
|
||||
local c = node.cache
|
||||
if not c then
|
||||
--[[
|
||||
if node.url:match("^(rttps?:)") then
|
||||
local s, response = rttp.get(node.url)
|
||||
c = s and response.statusCode == 200 and response.data
|
||||
else
|
||||
c = Util.httpGet(node.url)
|
||||
end
|
||||
]]--
|
||||
c = Util.httpGet(node.url)
|
||||
if c then
|
||||
node.cache = c
|
||||
node.size = #c
|
||||
|
@ -1,215 +1,584 @@
|
||||
-- credit ElvishJerricco
|
||||
-- http://pastebin.com/raw.php?i=4nRg9CHU
|
||||
-- Module options:
|
||||
local register_global_module_table = false
|
||||
local global_module_name = 'json'
|
||||
|
||||
local json = { }
|
||||
--[==[
|
||||
NOTE: Modified to reduce file size.
|
||||
See https://github.com/LuaDist/dkjson/blob/master/dkjson.lua
|
||||
for full version.
|
||||
|
||||
------------------------------------------------------------------ utils
|
||||
local controls = {["\n"]="\\n", ["\r"]="\\r", ["\t"]="\\t", ["\b"]="\\b", ["\f"]="\\f", ["\""]="\\\"", ["\\"]="\\\\"}
|
||||
David Kolf's JSON module for Lua 5.1/5.2
|
||||
Version 2.5
|
||||
|
||||
local function isArray(t)
|
||||
local max = 0
|
||||
for k,v in pairs(t) do
|
||||
if type(k) ~= "number" then
|
||||
return false
|
||||
elseif k > max then
|
||||
max = k
|
||||
end
|
||||
end
|
||||
return max == #t
|
||||
For the documentation see the corresponding readme.txt or visit
|
||||
<http://dkolf.de/src/dkjson-lua.fsl/>.
|
||||
|
||||
You can contact the author by sending an e-mail to 'david' at the
|
||||
domain 'dkolf.de'.
|
||||
|
||||
Copyright (C) 2010-2014 David Heiko Kolf
|
||||
|
||||
Refer to license located at https://github.com/LuaDist/dkjson/blob/master/dkjson.lua
|
||||
|
||||
--]==]
|
||||
|
||||
-- global dependencies:
|
||||
local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
|
||||
pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
|
||||
local error, require, pcall, select = error, require, pcall, select
|
||||
local floor, huge = math.floor, math.huge
|
||||
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
|
||||
string.rep, string.gsub, string.sub, string.byte, string.char,
|
||||
string.find, string.len, string.format
|
||||
local strmatch = string.match
|
||||
local concat = table.concat
|
||||
|
||||
local json = { version = "dkjson 2.5" }
|
||||
|
||||
if register_global_module_table then
|
||||
_G[global_module_name] = json
|
||||
end
|
||||
|
||||
local whites = {['\n']=true; ['\r']=true; ['\t']=true; [' ']=true; [',']=true; [':']=true}
|
||||
local function removeWhite(str)
|
||||
while whites[str:sub(1, 1)] do
|
||||
str = str:sub(2)
|
||||
end
|
||||
return str
|
||||
end
|
||||
local _ENV = nil -- blocking globals in Lua 5.2
|
||||
|
||||
------------------------------------------------------------------ encoding
|
||||
pcall (function()
|
||||
-- Enable access to blocked metatables.
|
||||
-- Don't worry, this module doesn't change anything in them.
|
||||
local debmeta = require "debug".getmetatable
|
||||
if debmeta then getmetatable = debmeta end
|
||||
end)
|
||||
|
||||
local function encodeCommon(val, pretty, tabLevel, tTracking)
|
||||
local str = ""
|
||||
json.null = setmetatable ({}, {
|
||||
__tojson = function () return "null" end
|
||||
})
|
||||
|
||||
-- Tabbing util
|
||||
local function tab(s)
|
||||
str = str .. ("\t"):rep(tabLevel) .. s
|
||||
end
|
||||
|
||||
local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc)
|
||||
str = str .. bracket
|
||||
if pretty then
|
||||
str = str .. "\n"
|
||||
tabLevel = tabLevel + 1
|
||||
end
|
||||
for k,v in iterator(val) do
|
||||
tab("")
|
||||
loopFunc(k,v)
|
||||
str = str .. ","
|
||||
if pretty then str = str .. "\n" end
|
||||
end
|
||||
if pretty then
|
||||
tabLevel = tabLevel - 1
|
||||
end
|
||||
if str:sub(-2) == ",\n" then
|
||||
str = str:sub(1, -3) .. "\n"
|
||||
elseif str:sub(-1) == "," then
|
||||
str = str:sub(1, -2)
|
||||
end
|
||||
tab(closeBracket)
|
||||
end
|
||||
|
||||
-- Table encoding
|
||||
if type(val) == "table" then
|
||||
assert(not tTracking[val], "Cannot encode a table holding itself recursively")
|
||||
tTracking[val] = true
|
||||
if isArray(val) then
|
||||
arrEncoding(val, "[", "]", ipairs, function(k,v)
|
||||
str = str .. encodeCommon(v, pretty, tabLevel, tTracking)
|
||||
end)
|
||||
local function isarray (tbl)
|
||||
local max, n, arraylen = 0, 0, 0
|
||||
for k,v in pairs (tbl) do
|
||||
if k == 'n' and type(v) == 'number' then
|
||||
arraylen = v
|
||||
if v > max then
|
||||
max = v
|
||||
end
|
||||
else
|
||||
arrEncoding(val, "{", "}", pairs, function(k,v)
|
||||
assert(type(k) == "string", "JSON object keys must be strings", 2)
|
||||
str = str .. encodeCommon(k, pretty, tabLevel, tTracking)
|
||||
str = str .. (pretty and ": " or ":") .. encodeCommon(v, pretty, tabLevel, tTracking)
|
||||
end)
|
||||
if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
|
||||
return false
|
||||
end
|
||||
if k > max then
|
||||
max = k
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
-- String encoding
|
||||
elseif type(val) == "string" then
|
||||
str = '"' .. val:gsub("[%c\"\\]", controls) .. '"'
|
||||
-- Number encoding
|
||||
elseif type(val) == "number" or type(val) == "boolean" then
|
||||
str = tostring(val)
|
||||
end
|
||||
if max > 10 and max > arraylen and max > n * 2 then
|
||||
return false -- don't create an array with too many holes
|
||||
end
|
||||
return true, max
|
||||
end
|
||||
|
||||
local escapecodes = {
|
||||
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
|
||||
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
|
||||
}
|
||||
|
||||
local function escapeutf8 (uchar)
|
||||
local value = escapecodes[uchar]
|
||||
if value then
|
||||
return value
|
||||
end
|
||||
local a, b, c, d = strbyte (uchar, 1, 4)
|
||||
a, b, c, d = a or 0, b or 0, c or 0, d or 0
|
||||
if a <= 0x7f then
|
||||
value = a
|
||||
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
|
||||
value = (a - 0xc0) * 0x40 + b - 0x80
|
||||
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
|
||||
value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
|
||||
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
|
||||
value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
|
||||
else
|
||||
error("JSON only supports arrays, objects, numbers, booleans, and strings", 2)
|
||||
return ""
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
function json.encode(val)
|
||||
return encodeCommon(val, false, 0, {})
|
||||
end
|
||||
|
||||
function json.encodePretty(val)
|
||||
return encodeCommon(val, true, 0, {})
|
||||
end
|
||||
|
||||
function json.encodeToFile(path, val)
|
||||
local file = io.open(path, "w")
|
||||
assert(file, "Unable to open file")
|
||||
file:write(json.encodePretty(val))
|
||||
file:close()
|
||||
end
|
||||
|
||||
------------------------------------------------------------------ decoding
|
||||
|
||||
local decodeControls = {}
|
||||
for k,v in pairs(controls) do
|
||||
decodeControls[v] = k
|
||||
end
|
||||
|
||||
local function parseBoolean(str)
|
||||
if str:sub(1, 4) == "true" then
|
||||
return true, removeWhite(str:sub(5))
|
||||
if value <= 0xffff then
|
||||
return strformat ("\\u%.4x", value)
|
||||
elseif value <= 0x10ffff then
|
||||
-- encode as UTF-16 surrogate pair
|
||||
value = value - 0x10000
|
||||
local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
|
||||
return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
|
||||
else
|
||||
return false, removeWhite(str:sub(6))
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
local function parseNull(str)
|
||||
return nil, removeWhite(str:sub(5))
|
||||
end
|
||||
|
||||
local numChars = {['e']=true; ['E']=true; ['+']=true; ['-']=true; ['.']=true}
|
||||
local function parseNumber(str)
|
||||
local i = 1
|
||||
while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do
|
||||
i = i + 1
|
||||
local function fsub (str, pattern, repl)
|
||||
-- gsub always builds a new string in a buffer, even when no match
|
||||
-- exists. First using find should be more efficient when most strings
|
||||
-- don't contain the pattern.
|
||||
if strfind (str, pattern) then
|
||||
return gsub (str, pattern, repl)
|
||||
else
|
||||
return str
|
||||
end
|
||||
local val = tonumber(str:sub(1, i - 1))
|
||||
str = removeWhite(str:sub(i))
|
||||
return val, str
|
||||
end
|
||||
|
||||
local function parseString(str)
|
||||
str = str:sub(2)
|
||||
local s = ""
|
||||
while str:sub(1,1) ~= "\"" do
|
||||
local next = str:sub(1,1)
|
||||
str = str:sub(2)
|
||||
assert(next ~= "\n", "Unclosed string")
|
||||
local function quotestring (value)
|
||||
-- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
|
||||
value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
|
||||
if strfind (value, "[\194\216\220\225\226\239]") then
|
||||
value = fsub (value, "\194[\128-\159\173]", escapeutf8)
|
||||
value = fsub (value, "\216[\128-\132]", escapeutf8)
|
||||
value = fsub (value, "\220\143", escapeutf8)
|
||||
value = fsub (value, "\225\158[\180\181]", escapeutf8)
|
||||
value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
|
||||
value = fsub (value, "\226\129[\160-\175]", escapeutf8)
|
||||
value = fsub (value, "\239\187\191", escapeutf8)
|
||||
value = fsub (value, "\239\191[\176-\191]", escapeutf8)
|
||||
end
|
||||
return "\"" .. value .. "\""
|
||||
end
|
||||
json.quotestring = quotestring
|
||||
|
||||
if next == "\\" then
|
||||
local escape = str:sub(1,1)
|
||||
str = str:sub(2)
|
||||
local function replace(str, o, n)
|
||||
local i, j = strfind (str, o, 1, true)
|
||||
if i then
|
||||
return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
|
||||
else
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
next = assert(decodeControls[next..escape], "Invalid escape character")
|
||||
-- locale independent num2str and str2num functions
|
||||
local decpoint, numfilter
|
||||
|
||||
local function updatedecpoint ()
|
||||
decpoint = strmatch(tostring(0.5), "([^05+])")
|
||||
-- build a filter that can be used to remove group separators
|
||||
numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
|
||||
end
|
||||
|
||||
updatedecpoint()
|
||||
|
||||
local function num2str (num)
|
||||
return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
|
||||
end
|
||||
|
||||
local function str2num (str)
|
||||
local num = tonumber(replace(str, ".", decpoint))
|
||||
if not num then
|
||||
updatedecpoint()
|
||||
num = tonumber(replace(str, ".", decpoint))
|
||||
end
|
||||
return num
|
||||
end
|
||||
|
||||
local function addnewline2 (level, buffer, buflen)
|
||||
buffer[buflen+1] = "\n"
|
||||
buffer[buflen+2] = strrep (" ", level)
|
||||
buflen = buflen + 2
|
||||
return buflen
|
||||
end
|
||||
|
||||
function json.addnewline (state)
|
||||
if state.indent then
|
||||
state.bufferlen = addnewline2 (state.level or 0,
|
||||
state.buffer, state.bufferlen or #(state.buffer))
|
||||
end
|
||||
end
|
||||
|
||||
local encode2 -- forward declaration
|
||||
|
||||
local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
local kt = type (key)
|
||||
if kt ~= 'string' and kt ~= 'number' then
|
||||
return nil, "type '" .. kt .. "' is not supported as a key by JSON."
|
||||
end
|
||||
if prev then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = ","
|
||||
end
|
||||
if indent then
|
||||
buflen = addnewline2 (level, buffer, buflen)
|
||||
end
|
||||
buffer[buflen+1] = quotestring (key)
|
||||
buffer[buflen+2] = ":"
|
||||
return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state)
|
||||
end
|
||||
|
||||
local function appendcustom(res, buffer, state)
|
||||
local buflen = state.bufferlen
|
||||
if type (res) == 'string' then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = res
|
||||
end
|
||||
return buflen
|
||||
end
|
||||
|
||||
local function exception(reason, value, state, buffer, buflen, defaultmessage)
|
||||
defaultmessage = defaultmessage or reason
|
||||
local handler = state.exception
|
||||
if not handler then
|
||||
return nil, defaultmessage
|
||||
else
|
||||
state.bufferlen = buflen
|
||||
local ret, msg = handler (reason, value, state, defaultmessage)
|
||||
if not ret then return nil, msg or defaultmessage end
|
||||
return appendcustom(ret, buffer, state)
|
||||
end
|
||||
end
|
||||
|
||||
function json.encodeexception(reason, value, state, defaultmessage)
|
||||
return quotestring("<" .. defaultmessage .. ">")
|
||||
end
|
||||
|
||||
encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
local valtype = type (value)
|
||||
local valmeta = getmetatable (value)
|
||||
valmeta = type (valmeta) == 'table' and valmeta -- only tables
|
||||
local valtojson = valmeta and valmeta.__tojson
|
||||
if valtojson then
|
||||
if tables[value] then
|
||||
return exception('reference cycle', value, state, buffer, buflen)
|
||||
end
|
||||
|
||||
s = s .. next
|
||||
tables[value] = true
|
||||
state.bufferlen = buflen
|
||||
local ret, msg = valtojson (value, state)
|
||||
if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
|
||||
tables[value] = nil
|
||||
buflen = appendcustom(ret, buffer, state)
|
||||
elseif value == nil then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "null"
|
||||
elseif valtype == 'number' then
|
||||
local s
|
||||
if value ~= value or value >= huge or -value >= huge then
|
||||
-- This is the behaviour of the original JSON implementation.
|
||||
s = "null"
|
||||
else
|
||||
s = num2str (value)
|
||||
end
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = s
|
||||
elseif valtype == 'boolean' then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = value and "true" or "false"
|
||||
elseif valtype == 'string' then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = quotestring (value)
|
||||
elseif valtype == 'table' then
|
||||
if tables[value] then
|
||||
return exception('reference cycle', value, state, buffer, buflen)
|
||||
end
|
||||
tables[value] = true
|
||||
level = level + 1
|
||||
local isa, n = isarray (value)
|
||||
if n == 0 and valmeta and valmeta.__jsontype == 'object' then
|
||||
isa = false
|
||||
end
|
||||
local msg
|
||||
if isa then -- JSON array
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "["
|
||||
for i = 1, n do
|
||||
buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state)
|
||||
if not buflen then return nil, msg end
|
||||
if i < n then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = ","
|
||||
end
|
||||
end
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "]"
|
||||
else -- JSON object
|
||||
local prev = false
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "{"
|
||||
local order = valmeta and valmeta.__jsonorder or globalorder
|
||||
if order then
|
||||
local used = {}
|
||||
n = #order
|
||||
for i = 1, n do
|
||||
local k = order[i]
|
||||
local v = value[k]
|
||||
if v then
|
||||
used[k] = true
|
||||
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
prev = true -- add a seperator before the next element
|
||||
end
|
||||
end
|
||||
for k,v in pairs (value) do
|
||||
if not used[k] then
|
||||
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
if not buflen then return nil, msg end
|
||||
prev = true -- add a seperator before the next element
|
||||
end
|
||||
end
|
||||
else -- unordered
|
||||
for k,v in pairs (value) do
|
||||
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
if not buflen then return nil, msg end
|
||||
prev = true -- add a seperator before the next element
|
||||
end
|
||||
end
|
||||
if indent then
|
||||
buflen = addnewline2 (level - 1, buffer, buflen)
|
||||
end
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = "}"
|
||||
end
|
||||
tables[value] = nil
|
||||
else
|
||||
return exception ('unsupported type', value, state, buffer, buflen,
|
||||
"type '" .. valtype .. "' is not supported by JSON.")
|
||||
end
|
||||
return s, removeWhite(str:sub(2))
|
||||
return buflen
|
||||
end
|
||||
|
||||
function json.parseArray(str)
|
||||
str = removeWhite(str:sub(2))
|
||||
|
||||
local val = {}
|
||||
local i = 1
|
||||
while str:sub(1, 1) ~= "]" do
|
||||
local v
|
||||
v, str = json.parseValue(str)
|
||||
val[i] = v
|
||||
i = i + 1
|
||||
str = removeWhite(str)
|
||||
end
|
||||
str = removeWhite(str:sub(2))
|
||||
return val, str
|
||||
end
|
||||
|
||||
function json.parseValue(str)
|
||||
local fchar = str:sub(1, 1)
|
||||
if fchar == "{" then
|
||||
return json.parseObject(str)
|
||||
elseif fchar == "[" then
|
||||
return json.parseArray(str)
|
||||
elseif tonumber(fchar) ~= nil or numChars[fchar] then
|
||||
return parseNumber(str)
|
||||
elseif str:sub(1, 4) == "true" or str:sub(1, 5) == "false" then
|
||||
return parseBoolean(str)
|
||||
elseif fchar == "\"" then
|
||||
return parseString(str)
|
||||
elseif str:sub(1, 4) == "null" then
|
||||
return parseNull(str)
|
||||
function json.encode (value, state)
|
||||
state = state or {}
|
||||
local oldbuffer = state.buffer
|
||||
local buffer = oldbuffer or {}
|
||||
state.buffer = buffer
|
||||
updatedecpoint()
|
||||
local ret, msg = encode2 (value, state.indent, state.level or 0,
|
||||
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
|
||||
if not ret then
|
||||
error (msg, 2)
|
||||
elseif oldbuffer == buffer then
|
||||
state.bufferlen = ret
|
||||
return true
|
||||
else
|
||||
state.bufferlen = nil
|
||||
state.buffer = nil
|
||||
return concat (buffer)
|
||||
end
|
||||
end
|
||||
|
||||
function json.parseMember(str)
|
||||
local k, val
|
||||
k, str = json.parseValue(str)
|
||||
val, str = json.parseValue(str)
|
||||
return k, val, str
|
||||
end
|
||||
|
||||
function json.parseObject(str)
|
||||
str = removeWhite(str:sub(2))
|
||||
|
||||
local val = {}
|
||||
while str:sub(1, 1) ~= "}" do
|
||||
local k, v
|
||||
k, v, str = json.parseMember(str)
|
||||
val[k] = v
|
||||
str = removeWhite(str)
|
||||
local function loc (str, where)
|
||||
local line, pos, linepos = 1, 1, 0
|
||||
while true do
|
||||
pos = strfind (str, "\n", pos, true)
|
||||
if pos and pos < where then
|
||||
line = line + 1
|
||||
linepos = pos
|
||||
pos = pos + 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
str = removeWhite(str:sub(2))
|
||||
return val, str
|
||||
return "line " .. line .. ", column " .. (where - linepos)
|
||||
end
|
||||
|
||||
function json.decode(str)
|
||||
str = removeWhite(str)
|
||||
return json.parseValue(str)
|
||||
local function unterminated (str, what, where)
|
||||
return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
|
||||
end
|
||||
|
||||
local function scanwhite (str, pos)
|
||||
while true do
|
||||
pos = strfind (str, "%S", pos)
|
||||
if not pos then return nil end
|
||||
local sub2 = strsub (str, pos, pos + 1)
|
||||
if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
|
||||
-- UTF-8 Byte Order Mark
|
||||
pos = pos + 3
|
||||
elseif sub2 == "//" then
|
||||
pos = strfind (str, "[\n\r]", pos + 2)
|
||||
if not pos then return nil end
|
||||
elseif sub2 == "/*" then
|
||||
pos = strfind (str, "*/", pos + 2)
|
||||
if not pos then return nil end
|
||||
pos = pos + 2
|
||||
else
|
||||
return pos
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local escapechars = {
|
||||
["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
|
||||
["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
|
||||
}
|
||||
|
||||
local function unichar (value)
|
||||
if value < 0 then
|
||||
return nil
|
||||
elseif value <= 0x007f then
|
||||
return strchar (value)
|
||||
elseif value <= 0x07ff then
|
||||
return strchar (0xc0 + floor(value/0x40),
|
||||
0x80 + (floor(value) % 0x40))
|
||||
elseif value <= 0xffff then
|
||||
return strchar (0xe0 + floor(value/0x1000),
|
||||
0x80 + (floor(value/0x40) % 0x40),
|
||||
0x80 + (floor(value) % 0x40))
|
||||
elseif value <= 0x10ffff then
|
||||
return strchar (0xf0 + floor(value/0x40000),
|
||||
0x80 + (floor(value/0x1000) % 0x40),
|
||||
0x80 + (floor(value/0x40) % 0x40),
|
||||
0x80 + (floor(value) % 0x40))
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function scanstring (str, pos)
|
||||
local lastpos = pos + 1
|
||||
local buffer, n = {}, 0
|
||||
while true do
|
||||
local nextpos = strfind (str, "[\"\\]", lastpos)
|
||||
if not nextpos then
|
||||
return unterminated (str, "string", pos)
|
||||
end
|
||||
if nextpos > lastpos then
|
||||
n = n + 1
|
||||
buffer[n] = strsub (str, lastpos, nextpos - 1)
|
||||
end
|
||||
if strsub (str, nextpos, nextpos) == "\"" then
|
||||
lastpos = nextpos + 1
|
||||
break
|
||||
else
|
||||
local escchar = strsub (str, nextpos + 1, nextpos + 1)
|
||||
local value
|
||||
if escchar == "u" then
|
||||
value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
|
||||
if value then
|
||||
local value2
|
||||
if 0xD800 <= value and value <= 0xDBff then
|
||||
-- we have the high surrogate of UTF-16. Check if there is a
|
||||
-- low surrogate escaped nearby to combine them.
|
||||
if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
|
||||
value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
|
||||
if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
|
||||
value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
|
||||
else
|
||||
value2 = nil -- in case it was out of range for a low surrogate
|
||||
end
|
||||
end
|
||||
end
|
||||
value = value and unichar (value)
|
||||
if value then
|
||||
if value2 then
|
||||
lastpos = nextpos + 12
|
||||
else
|
||||
lastpos = nextpos + 6
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not value then
|
||||
value = escapechars[escchar] or escchar
|
||||
lastpos = nextpos + 2
|
||||
end
|
||||
n = n + 1
|
||||
buffer[n] = value
|
||||
end
|
||||
end
|
||||
if n == 1 then
|
||||
return buffer[1], lastpos
|
||||
elseif n > 1 then
|
||||
return concat (buffer), lastpos
|
||||
else
|
||||
return "", lastpos
|
||||
end
|
||||
end
|
||||
|
||||
local scanvalue -- forward declaration
|
||||
|
||||
local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
|
||||
local len = strlen (str)
|
||||
local tbl, n = {}, 0
|
||||
local pos = startpos + 1
|
||||
if what == 'object' then
|
||||
setmetatable (tbl, objectmeta)
|
||||
else
|
||||
setmetatable (tbl, arraymeta)
|
||||
end
|
||||
while true do
|
||||
pos = scanwhite (str, pos)
|
||||
if not pos then return unterminated (str, what, startpos) end
|
||||
local char = strsub (str, pos, pos)
|
||||
if char == closechar then
|
||||
return tbl, pos + 1
|
||||
end
|
||||
local val1, err
|
||||
val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
|
||||
if err then return nil, pos, err end
|
||||
pos = scanwhite (str, pos)
|
||||
if not pos then return unterminated (str, what, startpos) end
|
||||
char = strsub (str, pos, pos)
|
||||
if char == ":" then
|
||||
if val1 == nil then
|
||||
return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
|
||||
end
|
||||
pos = scanwhite (str, pos + 1)
|
||||
if not pos then return unterminated (str, what, startpos) end
|
||||
local val2
|
||||
val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
|
||||
if err then return nil, pos, err end
|
||||
tbl[val1] = val2
|
||||
pos = scanwhite (str, pos)
|
||||
if not pos then return unterminated (str, what, startpos) end
|
||||
char = strsub (str, pos, pos)
|
||||
else
|
||||
n = n + 1
|
||||
tbl[n] = val1
|
||||
end
|
||||
if char == "," then
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
|
||||
pos = pos or 1
|
||||
pos = scanwhite (str, pos)
|
||||
if not pos then
|
||||
return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
|
||||
end
|
||||
local char = strsub (str, pos, pos)
|
||||
if char == "{" then
|
||||
return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
|
||||
elseif char == "[" then
|
||||
return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
|
||||
elseif char == "\"" then
|
||||
return scanstring (str, pos)
|
||||
else
|
||||
local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
|
||||
if pstart then
|
||||
local number = str2num (strsub (str, pstart, pend))
|
||||
if number then
|
||||
return number, pend + 1
|
||||
end
|
||||
end
|
||||
pstart, pend = strfind (str, "^%a%w*", pos)
|
||||
if pstart then
|
||||
local name = strsub (str, pstart, pend)
|
||||
if name == "true" then
|
||||
return true, pend + 1
|
||||
elseif name == "false" then
|
||||
return false, pend + 1
|
||||
elseif name == "null" then
|
||||
return nullval, pend + 1
|
||||
end
|
||||
end
|
||||
return nil, pos, "no valid JSON value at " .. loc (str, pos)
|
||||
end
|
||||
end
|
||||
|
||||
local function optionalmetatables(...)
|
||||
if select("#", ...) > 0 then
|
||||
return ...
|
||||
else
|
||||
return {__jsontype = 'object'}, {__jsontype = 'array'}
|
||||
end
|
||||
end
|
||||
|
||||
function json.decode (str, pos, nullval, ...)
|
||||
local objectmeta, arraymeta = optionalmetatables(...)
|
||||
return scanvalue (str, pos, nullval, objectmeta, arraymeta)
|
||||
end
|
||||
|
||||
-- NOTE: added method - not in original source
|
||||
function json.decodeFromFile(path)
|
||||
local file = assert(fs.open(path, "r"))
|
||||
local decoded = json.decode(file.readAll())
|
||||
|
@ -19,9 +19,9 @@ function Map.removeMatches(t, values)
|
||||
return true
|
||||
end
|
||||
|
||||
for k,v in pairs(t) do
|
||||
if matchAll(v) then
|
||||
t[k] = nil
|
||||
for _, key in pairs(Util.keys(t)) do
|
||||
if matchAll(t[key]) then
|
||||
t[key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -106,6 +106,17 @@ local function selectDestination(pts, box, grid)
|
||||
end
|
||||
end
|
||||
|
||||
local function updateCanvas(path)
|
||||
local t = { }
|
||||
for node in path:nodes() do
|
||||
table.insert(t, { x = node.x, y = node.y, z = node.z })
|
||||
end
|
||||
os.queueEvent('canvas', {
|
||||
type = 'canvas_path',
|
||||
data = t,
|
||||
})
|
||||
end
|
||||
|
||||
local function pathTo(dest, options)
|
||||
local blocks = options.blocks or turtle.getState().blocks or { }
|
||||
local dests = options.dest or { dest } -- support alternative destinations
|
||||
@ -156,6 +167,8 @@ local function pathTo(dest, options)
|
||||
if not path then
|
||||
Util.removeByValue(dests, dest)
|
||||
else
|
||||
updateCanvas(path)
|
||||
|
||||
path:filter()
|
||||
|
||||
for node in path:nodes() do
|
||||
@ -173,11 +186,19 @@ local function pathTo(dest, options)
|
||||
|
||||
-- use single turn method so the turtle doesn't turn around
|
||||
-- when encountering obstacles
|
||||
if not turtle.gotoSingleTurn(pt.x, pt.y, pt.z, pt.heading) then
|
||||
--if not turtle.goto(pt) then
|
||||
--if not turtle.gotoSingleTurn(pt.x, pt.y, pt.z, pt.heading) then
|
||||
pt.heading = nil
|
||||
if not turtle.go(pt) then
|
||||
local bpt = Point.nearestTo(turtle.point, pt)
|
||||
|
||||
if turtle.getFuelLevel() == 0 then
|
||||
return false, 'Out of fuel'
|
||||
end
|
||||
table.insert(blocks, bpt)
|
||||
os.queueEvent('canvas', {
|
||||
type = 'canvas_barrier',
|
||||
data = { bpt },
|
||||
})
|
||||
-- really need to check if the block we ran into was a turtle.
|
||||
-- if so, this block should be temporary (1-2 secs)
|
||||
|
||||
|
@ -131,8 +131,11 @@ end
|
||||
function Point.calculateMoves(pta, ptb, distance)
|
||||
local heading = pta.heading
|
||||
local moves = distance or Point.turtleDistance(pta, ptb)
|
||||
local weighted = moves
|
||||
|
||||
if (pta.heading % 2) == 0 and pta.z ~= ptb.z then
|
||||
moves = moves + 1
|
||||
weighted = weighted + .9
|
||||
if ptb.heading and (ptb.heading % 2 == 1) then
|
||||
heading = ptb.heading
|
||||
elseif ptb.z > pta.z then
|
||||
@ -142,6 +145,7 @@ function Point.calculateMoves(pta, ptb, distance)
|
||||
end
|
||||
elseif (pta.heading % 2) == 1 and pta.x ~= ptb.x then
|
||||
moves = moves + 1
|
||||
weighted = weighted + .9
|
||||
if ptb.heading and (ptb.heading % 2 == 0) then
|
||||
heading = ptb.heading
|
||||
elseif ptb.x > pta.x then
|
||||
@ -152,15 +156,18 @@ function Point.calculateMoves(pta, ptb, distance)
|
||||
end
|
||||
|
||||
if not ptb.heading then
|
||||
return moves, heading, moves
|
||||
return moves, heading, weighted
|
||||
end
|
||||
|
||||
-- need to know if we are in digging mode
|
||||
-- if so, we need to face blocks -- need a no-backwards flag
|
||||
|
||||
-- calc turns as slightly less than moves
|
||||
local weighted = moves
|
||||
-- local weighted = moves
|
||||
if heading ~= ptb.heading then
|
||||
local turns = Point.calculateTurns(heading, ptb.heading)
|
||||
moves = moves + turns
|
||||
local wturns = { [0] = 0, [1] = .9, [2] = 1.9 }
|
||||
local wturns = { [0] = 0, [1] = .9, [2] = 1.8 }
|
||||
weighted = weighted + wturns[turns]
|
||||
heading = ptb.heading
|
||||
end
|
||||
@ -233,7 +240,7 @@ end
|
||||
function Point.nearestTo(pta, ptb)
|
||||
local heading
|
||||
|
||||
if pta.x < ptb.x then
|
||||
if pta.x < ptb.x then
|
||||
heading = 0
|
||||
elseif pta.z < ptb.z then
|
||||
heading = 1
|
||||
|
@ -1,32 +0,0 @@
|
||||
local Socket = require('socket')
|
||||
|
||||
local Proxy = { }
|
||||
|
||||
function Proxy.create(remoteId, uri)
|
||||
local socket, msg = Socket.connect(remoteId, 188)
|
||||
|
||||
if not socket then
|
||||
error(msg)
|
||||
end
|
||||
|
||||
socket.co = coroutine.running()
|
||||
|
||||
socket:write(uri)
|
||||
local methods = socket:read(2) or error('Timed out')
|
||||
|
||||
local hijack = { }
|
||||
for _,method in pairs(methods) do
|
||||
hijack[method] = function(...)
|
||||
socket:write({ method, ... })
|
||||
local resp = socket:read()
|
||||
if not resp then
|
||||
error('timed out: ' .. method)
|
||||
end
|
||||
return table.unpack(resp)
|
||||
end
|
||||
end
|
||||
|
||||
return hijack, socket
|
||||
end
|
||||
|
||||
return Proxy
|
@ -1,95 +0,0 @@
|
||||
local device = _G.device
|
||||
local os = _G.os
|
||||
|
||||
local rttp = { }
|
||||
local computerId = os.getComputerID()
|
||||
|
||||
local function parse(url, default)
|
||||
-- initialize default parameters
|
||||
local parsed = {}
|
||||
local authority
|
||||
|
||||
for i,v in pairs(default or parsed) do parsed[i] = v end
|
||||
-- remove whitespace
|
||||
-- url = string.gsub(url, "%s", "")
|
||||
-- Decode unreserved characters
|
||||
url = string.gsub(url, "%%(%x%x)", function(hex)
|
||||
local char = string.char(tonumber(hex, 16))
|
||||
if string.match(char, "[a-zA-Z0-9._~-]") then
|
||||
return char
|
||||
end
|
||||
-- Hex encodings that are not unreserved must be preserved.
|
||||
return nil
|
||||
end)
|
||||
-- get fragment
|
||||
url = string.gsub(url, "#(.*)$", function(f)
|
||||
parsed.fragment = f
|
||||
return ""
|
||||
end)
|
||||
-- get scheme. Lower-case according to RFC 3986 section 3.1.
|
||||
url = string.gsub(url, "^(%w[%w.+-]*):",
|
||||
function(s) parsed.scheme = string.lower(s); return "" end)
|
||||
-- get authority
|
||||
url = string.gsub(url, "^//([^/]*)", function(n)
|
||||
authority = n
|
||||
return ""
|
||||
end)
|
||||
-- get query stringing
|
||||
url = string.gsub(url, "%?(.*)", function(q)
|
||||
parsed.query = q
|
||||
return ""
|
||||
end)
|
||||
-- get params
|
||||
url = string.gsub(url, "%;(.*)", function(p)
|
||||
parsed.params = p
|
||||
return ""
|
||||
end)
|
||||
|
||||
-- path is whatever was left
|
||||
parsed.path = url
|
||||
|
||||
-- Represents host:port, port = nil if not used.
|
||||
if authority then
|
||||
authority = string.gsub(authority, ":(%d+)$",
|
||||
function(p) parsed.port = tonumber(p); return "" end)
|
||||
if authority ~= "" then
|
||||
parsed.host = authority
|
||||
end
|
||||
end
|
||||
return parsed
|
||||
end
|
||||
|
||||
function rttp.get(url)
|
||||
local modem = device.wireless_modem or error('Modem not found')
|
||||
local parsed = parse(url, { port = 80 })
|
||||
|
||||
parsed.host = tonumber(parsed.host) or error('Invalid url')
|
||||
|
||||
for i = 16384, 32767 do
|
||||
if not modem.isOpen(i) then
|
||||
modem.open(i)
|
||||
local path = parsed.query and parsed.path .. '?' .. parsed.query or parsed.path
|
||||
|
||||
modem.transmit(parsed.port, parsed.host, {
|
||||
method = 'GET',
|
||||
replyAddress = computerId,
|
||||
replyPort = i,
|
||||
path = path,
|
||||
})
|
||||
local timerId = os.startTimer(3)
|
||||
repeat
|
||||
local event, id, dport, dhost, response = os.pullEvent()
|
||||
if event == 'modem_message' and
|
||||
dport == i and
|
||||
dhost == computerId and
|
||||
type(response) == 'table' then
|
||||
modem.close(i)
|
||||
return true, response
|
||||
end
|
||||
until event == 'timer' and id == timerId
|
||||
return false, 'timeout'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return rttp
|
200
sys/apis/sha2.lua
Normal file
200
sys/apis/sha2.lua
Normal file
@ -0,0 +1,200 @@
|
||||
-- SHA-256, HMAC and PBKDF2 functions in ComputerCraft
|
||||
-- By Anavrins
|
||||
|
||||
local bit = _G.bit
|
||||
|
||||
local mod32 = 2^32
|
||||
local band = bit32 and bit32.band or bit.band
|
||||
local bnot = bit32 and bit32.bnot or bit.bnot
|
||||
local bxor = bit32 and bit32.bxor or bit.bxor
|
||||
local blshift = bit32 and bit32.lshift or bit.blshift
|
||||
local upack = unpack
|
||||
|
||||
local function rrotate(n, b)
|
||||
local s = n/(2^b)
|
||||
local f = s%1
|
||||
return (s-f) + f*mod32
|
||||
end
|
||||
local function brshift(int, by)
|
||||
local s = int / (2^by)
|
||||
return s - s%1
|
||||
end
|
||||
|
||||
local H = {
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
||||
}
|
||||
|
||||
local K = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
||||
}
|
||||
|
||||
local function counter(incr)
|
||||
local t1, t2 = 0, 0
|
||||
if 0xFFFFFFFF - t1 < incr then
|
||||
t2 = t2 + 1
|
||||
t1 = incr - (0xFFFFFFFF - t1) - 1
|
||||
else t1 = t1 + incr
|
||||
end
|
||||
return t2, t1
|
||||
end
|
||||
|
||||
local function BE_toInt(bs, i)
|
||||
return blshift((bs[i] or 0), 24) + blshift((bs[i+1] or 0), 16) + blshift((bs[i+2] or 0), 8) + (bs[i+3] or 0)
|
||||
end
|
||||
|
||||
local function preprocess(data)
|
||||
local len = #data
|
||||
local proc = {}
|
||||
data[#data+1] = 0x80
|
||||
while #data%64~=56 do data[#data+1] = 0 end
|
||||
local blocks = math.ceil(#data/64)
|
||||
for i = 1, blocks do
|
||||
proc[i] = {}
|
||||
for j = 1, 16 do
|
||||
proc[i][j] = BE_toInt(data, 1+((i-1)*64)+((j-1)*4))
|
||||
end
|
||||
end
|
||||
proc[blocks][15], proc[blocks][16] = counter(len*8)
|
||||
return proc
|
||||
end
|
||||
|
||||
local function digestblock(w, C)
|
||||
for j = 17, 64 do
|
||||
--local v = w[j-15]
|
||||
local s0 = bxor(bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18)), brshift(w[j-15], 3))
|
||||
local s1 = bxor(bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19)), brshift(w[j-2], 10))
|
||||
w[j] = (w[j-16] + s0 + w[j-7] + s1)%mod32
|
||||
end
|
||||
local a, b, c, d, e, f, g, h = upack(C)
|
||||
for j = 1, 64 do
|
||||
local S1 = bxor(bxor(rrotate(e, 6), rrotate(e, 11)), rrotate(e, 25))
|
||||
local ch = bxor(band(e, f), band(bnot(e), g))
|
||||
local temp1 = (h + S1 + ch + K[j] + w[j])%mod32
|
||||
local S0 = bxor(bxor(rrotate(a, 2), rrotate(a, 13)), rrotate(a, 22))
|
||||
local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c))
|
||||
local temp2 = (S0 + maj)%mod32
|
||||
h, g, f, e, d, c, b, a = g, f, e, (d+temp1)%mod32, c, b, a, (temp1+temp2)%mod32
|
||||
end
|
||||
C[1] = (C[1] + a)%mod32
|
||||
C[2] = (C[2] + b)%mod32
|
||||
C[3] = (C[3] + c)%mod32
|
||||
C[4] = (C[4] + d)%mod32
|
||||
C[5] = (C[5] + e)%mod32
|
||||
C[6] = (C[6] + f)%mod32
|
||||
C[7] = (C[7] + g)%mod32
|
||||
C[8] = (C[8] + h)%mod32
|
||||
return C
|
||||
end
|
||||
|
||||
local mt = {
|
||||
__tostring = function(a) return string.char(unpack(a)) end,
|
||||
__index = {
|
||||
toHex = function(self) return ("%02x"):rep(#self):format(unpack(self)) end,
|
||||
isEqual = function(self, t)
|
||||
if type(t) ~= "table" then return false end
|
||||
if #self ~= #t then return false end
|
||||
local ret = 0
|
||||
for i = 1, #self do
|
||||
ret = bit32.bor(ret, bxor(self[i], t[i]))
|
||||
end
|
||||
return ret == 0
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
local function toBytes(t, n)
|
||||
local b = {}
|
||||
for i = 1, n do
|
||||
b[(i-1)*4+1] = band(brshift(t[i], 24), 0xFF)
|
||||
b[(i-1)*4+2] = band(brshift(t[i], 16), 0xFF)
|
||||
b[(i-1)*4+3] = band(brshift(t[i], 8), 0xFF)
|
||||
b[(i-1)*4+4] = band(t[i], 0xFF)
|
||||
end
|
||||
return setmetatable(b, mt)
|
||||
end
|
||||
|
||||
local function digest(data)
|
||||
data = data or ""
|
||||
data = type(data) == "table" and {upack(data)} or {tostring(data):byte(1,-1)}
|
||||
|
||||
data = preprocess(data)
|
||||
local C = {upack(H)}
|
||||
for i = 1, #data do C = digestblock(data[i], C) end
|
||||
return toBytes(C, 8)
|
||||
end
|
||||
|
||||
local function hmac(data, key)
|
||||
data = type(data) == "table" and {upack(data)} or {tostring(data):byte(1,-1)}
|
||||
key = type(key) == "table" and {upack(key)} or {tostring(key):byte(1,-1)}
|
||||
|
||||
local blocksize = 64
|
||||
|
||||
key = #key > blocksize and digest(key) or key
|
||||
|
||||
local ipad = {}
|
||||
local opad = {}
|
||||
local padded_key = {}
|
||||
|
||||
for i = 1, blocksize do
|
||||
ipad[i] = bxor(0x36, key[i] or 0)
|
||||
opad[i] = bxor(0x5C, key[i] or 0)
|
||||
end
|
||||
|
||||
for i = 1, #data do
|
||||
ipad[blocksize+i] = data[i]
|
||||
end
|
||||
|
||||
ipad = digest(ipad)
|
||||
|
||||
for i = 1, blocksize do
|
||||
padded_key[i] = opad[i]
|
||||
padded_key[blocksize+i] = ipad[i]
|
||||
end
|
||||
|
||||
return digest(padded_key)
|
||||
end
|
||||
|
||||
local function pbkdf2(pass, salt, iter, dklen)
|
||||
local hashlen = 32
|
||||
local block = 1
|
||||
local out = {}
|
||||
|
||||
dklen = dklen or 32
|
||||
salt = type(salt) == "table" and salt or {tostring(salt):byte(1,-1)}
|
||||
|
||||
while dklen > 0 do
|
||||
local ikey = {}
|
||||
local isalt = {upack(salt)}
|
||||
local clen = dklen > hashlen and hashlen or dklen
|
||||
|
||||
isalt[#isalt+1] = band(brshift(block, 24), 0xFF)
|
||||
isalt[#isalt+1] = band(brshift(block, 16), 0xFF)
|
||||
isalt[#isalt+1] = band(brshift(block, 8), 0xFF)
|
||||
isalt[#isalt+1] = band(block, 0xFF)
|
||||
|
||||
for j = 1, iter do
|
||||
isalt = hmac(isalt, pass)
|
||||
for k = 1, clen do ikey[k] = bxor(isalt[k], ikey[k] or 0) end
|
||||
if j % 200 == 0 then os.queueEvent("PBKDF2", j) coroutine.yield("PBKDF2") end
|
||||
end
|
||||
dklen = dklen - clen
|
||||
block = block+1
|
||||
for k = 1, clen do out[#out+1] = ikey[k] end
|
||||
end
|
||||
|
||||
return setmetatable(out, mt)
|
||||
end
|
||||
|
||||
return {
|
||||
digest = digest,
|
||||
hmac = hmac,
|
||||
pbkdf2 = pbkdf2,
|
||||
}
|
@ -1,61 +1,61 @@
|
||||
local Sync = {
|
||||
syncLocks = { }
|
||||
syncLocks = { }
|
||||
}
|
||||
|
||||
local os = _G.os
|
||||
|
||||
function Sync.sync(obj, fn)
|
||||
local key = tostring(obj)
|
||||
if Sync.syncLocks[key] then
|
||||
local cos = tostring(coroutine.running())
|
||||
table.insert(Sync.syncLocks[key], cos)
|
||||
repeat
|
||||
local _, co = os.pullEvent('sync_lock')
|
||||
until co == cos
|
||||
else
|
||||
Sync.syncLocks[key] = { }
|
||||
end
|
||||
local s, m = pcall(fn)
|
||||
local co = table.remove(Sync.syncLocks[key], 1)
|
||||
if co then
|
||||
os.queueEvent('sync_lock', co)
|
||||
else
|
||||
Sync.syncLocks[key] = nil
|
||||
end
|
||||
if not s then
|
||||
error(m)
|
||||
end
|
||||
local key = tostring(obj)
|
||||
if Sync.syncLocks[key] then
|
||||
local cos = tostring(coroutine.running())
|
||||
table.insert(Sync.syncLocks[key], cos)
|
||||
repeat
|
||||
local _, co = os.pullEvent('sync_lock')
|
||||
until co == cos
|
||||
else
|
||||
Sync.syncLocks[key] = { }
|
||||
end
|
||||
local s, m = pcall(fn)
|
||||
local co = table.remove(Sync.syncLocks[key], 1)
|
||||
if co then
|
||||
os.queueEvent('sync_lock', co)
|
||||
else
|
||||
Sync.syncLocks[key] = nil
|
||||
end
|
||||
if not s then
|
||||
error(m)
|
||||
end
|
||||
end
|
||||
|
||||
function Sync.lock(obj)
|
||||
local key = tostring(obj)
|
||||
if Sync.syncLocks[key] then
|
||||
local cos = tostring(coroutine.running())
|
||||
table.insert(Sync.syncLocks[key], cos)
|
||||
repeat
|
||||
local _, co = os.pullEvent('sync_lock')
|
||||
until co == cos
|
||||
else
|
||||
Sync.syncLocks[key] = { }
|
||||
end
|
||||
local key = tostring(obj)
|
||||
if Sync.syncLocks[key] then
|
||||
local cos = tostring(coroutine.running())
|
||||
table.insert(Sync.syncLocks[key], cos)
|
||||
repeat
|
||||
local _, co = os.pullEvent('sync_lock')
|
||||
until co == cos
|
||||
else
|
||||
Sync.syncLocks[key] = { }
|
||||
end
|
||||
end
|
||||
|
||||
function Sync.release(obj)
|
||||
local key = tostring(obj)
|
||||
if not Sync.syncLocks[key] then
|
||||
error('Sync.release: Lock was not obtained', 2)
|
||||
end
|
||||
local co = table.remove(Sync.syncLocks[key], 1)
|
||||
if co then
|
||||
os.queueEvent('sync_lock', co)
|
||||
else
|
||||
Sync.syncLocks[key] = nil
|
||||
end
|
||||
local key = tostring(obj)
|
||||
if not Sync.syncLocks[key] then
|
||||
error('Sync.release: Lock was not obtained', 2)
|
||||
end
|
||||
local co = table.remove(Sync.syncLocks[key], 1)
|
||||
if co then
|
||||
os.queueEvent('sync_lock', co)
|
||||
else
|
||||
Sync.syncLocks[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Sync.isLocked(obj)
|
||||
local key = tostring(obj)
|
||||
return not not Sync.syncLocks[key]
|
||||
local key = tostring(obj)
|
||||
return not not Sync.syncLocks[key]
|
||||
end
|
||||
|
||||
return Sync
|
||||
|
@ -5,106 +5,106 @@ local type = type
|
||||
local debug_traceback = type(debug) == "table" and type(debug.traceback) == "function" and debug.traceback
|
||||
|
||||
local function traceback(x)
|
||||
-- Attempt to detect error() and error("xyz", 0).
|
||||
-- This probably means they're erroring the program intentionally and so we
|
||||
-- shouldn't display anything.
|
||||
if x == nil or (type(x) == "string" and not x:find(":%d+:")) then
|
||||
return x
|
||||
end
|
||||
-- Attempt to detect error() and error("xyz", 0).
|
||||
-- This probably means they're erroring the program intentionally and so we
|
||||
-- shouldn't display anything.
|
||||
if x == nil or (type(x) == "string" and not x:find(":%d+:")) then
|
||||
return x
|
||||
end
|
||||
|
||||
if debug_traceback then
|
||||
-- The parens are important, as they prevent a tail call occuring, meaning
|
||||
-- the stack level is preserved. This ensures the code behaves identically
|
||||
-- on LuaJ and PUC Lua.
|
||||
return (debug_traceback(tostring(x), 2))
|
||||
else
|
||||
local level = 3
|
||||
local out = { tostring(x), "stack traceback:" }
|
||||
while true do
|
||||
local _, msg = pcall(error, "", level)
|
||||
if msg == "" then break end
|
||||
if debug_traceback then
|
||||
-- The parens are important, as they prevent a tail call occuring, meaning
|
||||
-- the stack level is preserved. This ensures the code behaves identically
|
||||
-- on LuaJ and PUC Lua.
|
||||
return (debug_traceback(tostring(x), 2))
|
||||
else
|
||||
local level = 3
|
||||
local out = { tostring(x), "stack traceback:" }
|
||||
while true do
|
||||
local _, msg = pcall(error, "", level)
|
||||
if msg == "" then break end
|
||||
|
||||
out[#out + 1] = " " .. msg
|
||||
level = level + 1
|
||||
end
|
||||
out[#out + 1] = " " .. msg
|
||||
level = level + 1
|
||||
end
|
||||
|
||||
return table.concat(out, "\n")
|
||||
end
|
||||
return table.concat(out, "\n")
|
||||
end
|
||||
end
|
||||
|
||||
local function trim_traceback(target, marker)
|
||||
local ttarget, tmarker = {}, {}
|
||||
for line in target:gmatch("([^\n]*)\n?") do ttarget[#ttarget + 1] = line end
|
||||
for line in marker:gmatch("([^\n]*)\n?") do tmarker[#tmarker + 1] = line end
|
||||
local ttarget, tmarker = {}, {}
|
||||
for line in target:gmatch("([^\n]*)\n?") do ttarget[#ttarget + 1] = line end
|
||||
for line in marker:gmatch("([^\n]*)\n?") do tmarker[#tmarker + 1] = line end
|
||||
|
||||
-- Trim identical suffixes
|
||||
local t_len, m_len = #ttarget, #tmarker
|
||||
while t_len >= 3 and ttarget[t_len] == tmarker[m_len] do
|
||||
table.remove(ttarget, t_len)
|
||||
t_len, m_len = t_len - 1, m_len - 1
|
||||
end
|
||||
-- Trim identical suffixes
|
||||
local t_len, m_len = #ttarget, #tmarker
|
||||
while t_len >= 3 and ttarget[t_len] == tmarker[m_len] do
|
||||
table.remove(ttarget, t_len)
|
||||
t_len, m_len = t_len - 1, m_len - 1
|
||||
end
|
||||
|
||||
-- Trim elements from this file and xpcall invocations
|
||||
while t_len >= 1 and ttarget[t_len]:find("^\tstack_trace%.lua:%d+:") or
|
||||
ttarget[t_len] == "\t[C]: in function 'xpcall'" or ttarget[t_len] == " xpcall: " do
|
||||
table.remove(ttarget, t_len)
|
||||
t_len = t_len - 1
|
||||
end
|
||||
-- Trim elements from this file and xpcall invocations
|
||||
while t_len >= 1 and ttarget[t_len]:find("^\tstack_trace%.lua:%d+:") or
|
||||
ttarget[t_len] == "\t[C]: in function 'xpcall'" or ttarget[t_len] == " xpcall: " do
|
||||
table.remove(ttarget, t_len)
|
||||
t_len = t_len - 1
|
||||
end
|
||||
|
||||
ttarget[#ttarget] = nil -- remove 2 calls added by the added xpcall
|
||||
ttarget[#ttarget] = nil
|
||||
ttarget[#ttarget] = nil -- remove 2 calls added by the added xpcall
|
||||
ttarget[#ttarget] = nil
|
||||
|
||||
return ttarget
|
||||
return ttarget
|
||||
end
|
||||
|
||||
--- Run a function with
|
||||
return function (fn, ...)
|
||||
-- So this is rather grim: we need to get the full traceback and current one and remove
|
||||
-- the common prefix
|
||||
local trace
|
||||
local args = { ... }
|
||||
-- So this is rather grim: we need to get the full traceback and current one and remove
|
||||
-- the common prefix
|
||||
local trace
|
||||
local args = { ... }
|
||||
|
||||
-- xpcall in Lua 5.1 does not accept parameters
|
||||
-- which is not ideal
|
||||
local res = table.pack(xpcall(function()
|
||||
return fn(table.unpack(args))
|
||||
end, traceback))
|
||||
-- xpcall in Lua 5.1 does not accept parameters
|
||||
-- which is not ideal
|
||||
local res = table.pack(xpcall(function()
|
||||
return fn(table.unpack(args))
|
||||
end, traceback))
|
||||
|
||||
if not res[1] then
|
||||
trace = traceback("trace.lua:1:")
|
||||
end
|
||||
local ok, err = res[1], res[2]
|
||||
if not res[1] then
|
||||
trace = traceback("trace.lua:1:")
|
||||
end
|
||||
local ok, err = res[1], res[2]
|
||||
|
||||
if not ok and err ~= nil then
|
||||
trace = trim_traceback(err, trace)
|
||||
if not ok and err ~= nil then
|
||||
trace = trim_traceback(err, trace)
|
||||
|
||||
-- Find the position where the stack traceback actually starts
|
||||
local trace_starts
|
||||
for i = #trace, 1, -1 do
|
||||
if trace[i] == "stack traceback:" then trace_starts = i; break end
|
||||
end
|
||||
-- Find the position where the stack traceback actually starts
|
||||
local trace_starts
|
||||
for i = #trace, 1, -1 do
|
||||
if trace[i] == "stack traceback:" then trace_starts = i; break end
|
||||
end
|
||||
|
||||
for _, line in pairs(trace) do
|
||||
_G._syslog(line)
|
||||
end
|
||||
for _, line in pairs(trace) do
|
||||
_G._syslog(line)
|
||||
end
|
||||
|
||||
-- If this traceback is more than 15 elements long, keep the first 9, last 5
|
||||
-- and put an ellipsis between the rest
|
||||
local max = 10
|
||||
if trace_starts and #trace - trace_starts > max then
|
||||
local keep_starts = trace_starts + 7
|
||||
for i = #trace - trace_starts - max, 0, -1 do
|
||||
table.remove(trace, keep_starts + i)
|
||||
end
|
||||
table.insert(trace, keep_starts, " ...")
|
||||
end
|
||||
-- If this traceback is more than 15 elements long, keep the first 9, last 5
|
||||
-- and put an ellipsis between the rest
|
||||
local max = 10
|
||||
if trace_starts and #trace - trace_starts > max then
|
||||
local keep_starts = trace_starts + 7
|
||||
for i = #trace - trace_starts - max, 0, -1 do
|
||||
table.remove(trace, keep_starts + i)
|
||||
end
|
||||
table.insert(trace, keep_starts, " ...")
|
||||
end
|
||||
|
||||
for k, line in pairs(trace) do
|
||||
trace[k] = line:gsub("in function", " in")
|
||||
end
|
||||
for k, line in pairs(trace) do
|
||||
trace[k] = line:gsub("in function", " in")
|
||||
end
|
||||
|
||||
return false, table.remove(trace, 1), table.concat(trace, "\n")
|
||||
end
|
||||
return false, table.remove(trace, 1), table.concat(trace, "\n")
|
||||
end
|
||||
|
||||
return table.unpack(res, 1, res.n)
|
||||
return table.unpack(res, 1, res.n)
|
||||
end
|
||||
|
@ -329,23 +329,13 @@ function UI.Grid:drawRows()
|
||||
local rawRow = self.values[key]
|
||||
local row = self:getDisplayValues(rawRow, key)
|
||||
|
||||
local ind = ' '
|
||||
if self.focused and index == self.index and not self.inactive then
|
||||
ind = self.focusIndicator
|
||||
end
|
||||
|
||||
local selected = index == self.index and not self.inactive
|
||||
local bg = self:getRowBackgroundColor(rawRow, selected)
|
||||
local fg = self:getRowTextColor(rawRow, selected)
|
||||
local focused = self.focused and selected
|
||||
|
||||
self:drawRow(sb, row, focused, bg, fg)
|
||||
|
||||
for _,col in pairs(self.columns) do
|
||||
sb:write(ind .. safeValue(row[col.key] or ''),
|
||||
col.cw + 1,
|
||||
col.align,
|
||||
bg,
|
||||
fg)
|
||||
ind = ' '
|
||||
end
|
||||
sb:finish(bg)
|
||||
end
|
||||
|
||||
@ -354,6 +344,19 @@ function UI.Grid:drawRows()
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Grid:drawRow(sb, row, focused, bg, fg)
|
||||
local ind = focused and self.focusIndicator or ' '
|
||||
|
||||
for _,col in pairs(self.columns) do
|
||||
sb:write(ind .. safeValue(row[col.key] or ''),
|
||||
col.cw + 1,
|
||||
col.align,
|
||||
bg,
|
||||
fg)
|
||||
ind = ' '
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Grid:getRowTextColor(row, selected)
|
||||
if selected then
|
||||
if self.focused then
|
||||
|
@ -560,7 +560,7 @@ function Util.insertString(str, istr, pos)
|
||||
end
|
||||
|
||||
function Util.split(str, pattern)
|
||||
if not str then error('Util.split: Invalid parameters', 2) end
|
||||
if not str or type(str) ~= 'string' then error('Util.split: Invalid parameters', 2) end
|
||||
pattern = pattern or "(.-)\n"
|
||||
local t = {}
|
||||
local function helper(line) table.insert(t, line) return "" end
|
||||
|
@ -15,11 +15,11 @@ local FILE = 1
|
||||
UI:configure('Files', ...)
|
||||
|
||||
local config = Config.load('Files', {
|
||||
showHidden = false,
|
||||
showDirSizes = false,
|
||||
showHidden = false,
|
||||
showDirSizes = false,
|
||||
})
|
||||
config.associations = config.associations or {
|
||||
nft = 'pain',
|
||||
nft = 'pain',
|
||||
}
|
||||
|
||||
local copied = { }
|
||||
@ -28,517 +28,517 @@ local directories = { }
|
||||
local cutMode = false
|
||||
|
||||
local function formatSize(size)
|
||||
if size >= 1000000 then
|
||||
return string.format('%dM', math.floor(size/1000000, 2))
|
||||
elseif size >= 1000 then
|
||||
return string.format('%dK', math.floor(size/1000, 2))
|
||||
end
|
||||
return size
|
||||
if size >= 1000000 then
|
||||
return string.format('%dM', math.floor(size/1000000, 2))
|
||||
elseif size >= 1000 then
|
||||
return string.format('%dK', math.floor(size/1000, 2))
|
||||
end
|
||||
return size
|
||||
end
|
||||
|
||||
local Browser = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = '^-', event = 'updir' },
|
||||
{ text = 'File', dropdown = {
|
||||
{ text = 'Run', event = 'run', flags = FILE },
|
||||
{ text = 'Edit e', event = 'edit', flags = FILE },
|
||||
{ text = 'Cloud edit c', event = 'cedit', flags = FILE },
|
||||
{ text = 'Pastebin put p', event = 'pastebin', flags = FILE },
|
||||
{ text = 'Shell s', event = 'shell' },
|
||||
{ spacer = true },
|
||||
{ text = 'Quit q', event = 'quit' },
|
||||
} },
|
||||
{ text = 'Edit', dropdown = {
|
||||
{ text = 'Cut ^x', event = 'cut' },
|
||||
{ text = 'Copy ^c', event = 'copy' },
|
||||
{ text = 'Copy path ', event = 'copy_path' },
|
||||
{ text = 'Paste ^v', event = 'paste' },
|
||||
{ spacer = true },
|
||||
{ text = 'Mark m', event = 'mark' },
|
||||
{ text = 'Unmark all u', event = 'unmark' },
|
||||
{ spacer = true },
|
||||
{ text = 'Delete del', event = 'delete' },
|
||||
} },
|
||||
{ text = 'View', dropdown = {
|
||||
{ text = 'Refresh r', event = 'refresh' },
|
||||
{ text = 'Hidden ^h', event = 'toggle_hidden' },
|
||||
{ text = 'Dir Size ^s', event = 'toggle_dirSize' },
|
||||
} },
|
||||
{ text = '\187',
|
||||
x = -3,
|
||||
dropdown = {
|
||||
{ text = 'Associations', event = 'associate' },
|
||||
} },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ key = 'flags', width = 2 },
|
||||
{ heading = 'Size', key = 'fsize', width = 5 },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
y = 2, ey = -2,
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
columns = {
|
||||
{ key = 'status' },
|
||||
{ key = 'totalSize', width = 6 },
|
||||
},
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
associations = UI.SlideOut {
|
||||
backgroundColor = colors.cyan,
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Save', event = 'save' },
|
||||
{ text = 'Cancel', event = 'cancel' },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, ex = -6, y = 3, ey = -5,
|
||||
columns = {
|
||||
{ heading = 'Extension', key = 'name' },
|
||||
{ heading = 'Program', key = 'value' },
|
||||
},
|
||||
autospace = true,
|
||||
sortColumn = 'name',
|
||||
accelerators = {
|
||||
delete = 'remove_entry',
|
||||
},
|
||||
},
|
||||
remove = UI.Button {
|
||||
x = -4, y = 6,
|
||||
text = '-', event = 'remove_entry', help = 'Remove',
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 3, y = -3, ey = -2,
|
||||
margin = 1,
|
||||
manualControls = true,
|
||||
[1] = UI.TextEntry {
|
||||
width = 20,
|
||||
formLabel = 'Extension', formKey = 'name',
|
||||
shadowText = 'extension',
|
||||
required = true,
|
||||
limit = 64,
|
||||
},
|
||||
[2] = UI.TextEntry {
|
||||
width = 20,
|
||||
formLabel = 'Program', formKey = 'value',
|
||||
shadowText = 'program',
|
||||
required = true,
|
||||
limit = 128,
|
||||
},
|
||||
add = UI.Button {
|
||||
x = -11, y = 1,
|
||||
text = 'Add', event = 'add_association',
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
backgroundColor = colors.cyan,
|
||||
},
|
||||
},
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
c = 'cedit',
|
||||
e = 'edit',
|
||||
s = 'shell',
|
||||
p = 'pastebin',
|
||||
r = 'refresh',
|
||||
[ ' ' ] = 'mark',
|
||||
m = 'mark',
|
||||
backspace = 'updir',
|
||||
u = 'unmark',
|
||||
d = 'delete',
|
||||
delete = 'delete',
|
||||
[ 'control-h' ] = 'toggle_hidden',
|
||||
[ 'control-s' ] = 'toggle_dirSize',
|
||||
[ 'control-x' ] = 'cut',
|
||||
[ 'control-c' ] = 'copy',
|
||||
paste = 'paste',
|
||||
},
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = '^-', event = 'updir' },
|
||||
{ text = 'File', dropdown = {
|
||||
{ text = 'Run', event = 'run', flags = FILE },
|
||||
{ text = 'Edit e', event = 'edit', flags = FILE },
|
||||
{ text = 'Cloud edit c', event = 'cedit', flags = FILE },
|
||||
{ text = 'Pastebin put p', event = 'pastebin', flags = FILE },
|
||||
{ text = 'Shell s', event = 'shell' },
|
||||
{ spacer = true },
|
||||
{ text = 'Quit q', event = 'quit' },
|
||||
} },
|
||||
{ text = 'Edit', dropdown = {
|
||||
{ text = 'Cut ^x', event = 'cut' },
|
||||
{ text = 'Copy ^c', event = 'copy' },
|
||||
{ text = 'Copy path ', event = 'copy_path' },
|
||||
{ text = 'Paste ^v', event = 'paste' },
|
||||
{ spacer = true },
|
||||
{ text = 'Mark m', event = 'mark' },
|
||||
{ text = 'Unmark all u', event = 'unmark' },
|
||||
{ spacer = true },
|
||||
{ text = 'Delete del', event = 'delete' },
|
||||
} },
|
||||
{ text = 'View', dropdown = {
|
||||
{ text = 'Refresh r', event = 'refresh' },
|
||||
{ text = 'Hidden ^h', event = 'toggle_hidden' },
|
||||
{ text = 'Dir Size ^s', event = 'toggle_dirSize' },
|
||||
} },
|
||||
{ text = '\187',
|
||||
x = -3,
|
||||
dropdown = {
|
||||
{ text = 'Associations', event = 'associate' },
|
||||
} },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ key = 'flags', width = 2 },
|
||||
{ heading = 'Size', key = 'fsize', width = 5 },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
y = 2, ey = -2,
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
columns = {
|
||||
{ key = 'status' },
|
||||
{ key = 'totalSize', width = 6 },
|
||||
},
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
associations = UI.SlideOut {
|
||||
backgroundColor = colors.cyan,
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Save', event = 'save' },
|
||||
{ text = 'Cancel', event = 'cancel' },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, ex = -6, y = 3, ey = -5,
|
||||
columns = {
|
||||
{ heading = 'Extension', key = 'name' },
|
||||
{ heading = 'Program', key = 'value' },
|
||||
},
|
||||
autospace = true,
|
||||
sortColumn = 'name',
|
||||
accelerators = {
|
||||
delete = 'remove_entry',
|
||||
},
|
||||
},
|
||||
remove = UI.Button {
|
||||
x = -4, y = 6,
|
||||
text = '-', event = 'remove_entry', help = 'Remove',
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 3, y = -3, ey = -2,
|
||||
margin = 1,
|
||||
manualControls = true,
|
||||
[1] = UI.TextEntry {
|
||||
width = 20,
|
||||
formLabel = 'Extension', formKey = 'name',
|
||||
shadowText = 'extension',
|
||||
required = true,
|
||||
limit = 64,
|
||||
},
|
||||
[2] = UI.TextEntry {
|
||||
width = 20,
|
||||
formLabel = 'Program', formKey = 'value',
|
||||
shadowText = 'program',
|
||||
required = true,
|
||||
limit = 128,
|
||||
},
|
||||
add = UI.Button {
|
||||
x = -11, y = 1,
|
||||
text = 'Add', event = 'add_association',
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
backgroundColor = colors.cyan,
|
||||
},
|
||||
},
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
c = 'cedit',
|
||||
e = 'edit',
|
||||
s = 'shell',
|
||||
p = 'pastebin',
|
||||
r = 'refresh',
|
||||
[ ' ' ] = 'mark',
|
||||
m = 'mark',
|
||||
backspace = 'updir',
|
||||
u = 'unmark',
|
||||
d = 'delete',
|
||||
delete = 'delete',
|
||||
[ 'control-h' ] = 'toggle_hidden',
|
||||
[ 'control-s' ] = 'toggle_dirSize',
|
||||
[ 'control-x' ] = 'cut',
|
||||
[ 'control-c' ] = 'copy',
|
||||
paste = 'paste',
|
||||
},
|
||||
}
|
||||
|
||||
function Browser:enable()
|
||||
UI.Page.enable(self)
|
||||
self:setFocus(self.grid)
|
||||
UI.Page.enable(self)
|
||||
self:setFocus(self.grid)
|
||||
end
|
||||
|
||||
function Browser.menuBar:getActive(menuItem)
|
||||
local file = Browser.grid:getSelected()
|
||||
if menuItem.flags == FILE then
|
||||
return file and not file.isDir
|
||||
end
|
||||
return true
|
||||
local file = Browser.grid:getSelected()
|
||||
if menuItem.flags == FILE then
|
||||
return file and not file.isDir
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Browser.grid:sortCompare(a, b)
|
||||
if self.sortColumn == 'fsize' then
|
||||
return a.size < b.size
|
||||
elseif self.sortColumn == 'flags' then
|
||||
return a.flags < b.flags
|
||||
end
|
||||
if a.isDir == b.isDir then
|
||||
return a.name:lower() < b.name:lower()
|
||||
end
|
||||
return a.isDir
|
||||
if self.sortColumn == 'fsize' then
|
||||
return a.size < b.size
|
||||
elseif self.sortColumn == 'flags' then
|
||||
return a.flags < b.flags
|
||||
end
|
||||
if a.isDir == b.isDir then
|
||||
return a.name:lower() < b.name:lower()
|
||||
end
|
||||
return a.isDir
|
||||
end
|
||||
|
||||
function Browser.grid:getRowTextColor(file)
|
||||
if file.marked then
|
||||
return colors.green
|
||||
end
|
||||
if file.isDir then
|
||||
return colors.cyan
|
||||
end
|
||||
if file.isReadOnly then
|
||||
return colors.pink
|
||||
end
|
||||
return colors.white
|
||||
if file.marked then
|
||||
return colors.green
|
||||
end
|
||||
if file.isDir then
|
||||
return colors.cyan
|
||||
end
|
||||
if file.isReadOnly then
|
||||
return colors.pink
|
||||
end
|
||||
return colors.white
|
||||
end
|
||||
|
||||
function Browser.grid:eventHandler(event)
|
||||
if event.type == 'copy' then -- let copy be handled by parent
|
||||
return false
|
||||
end
|
||||
return UI.ScrollingGrid.eventHandler(self, event)
|
||||
if event.type == 'copy' then -- let copy be handled by parent
|
||||
return false
|
||||
end
|
||||
return UI.ScrollingGrid.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function Browser.statusBar:draw()
|
||||
if self.parent.dir then
|
||||
local info = '#:' .. Util.size(self.parent.dir.files)
|
||||
local numMarked = Util.size(marked)
|
||||
if numMarked > 0 then
|
||||
info = info .. ' M:' .. numMarked
|
||||
end
|
||||
self:setValue('info', info)
|
||||
self:setValue('totalSize', formatSize(self.parent.dir.totalSize))
|
||||
UI.StatusBar.draw(self)
|
||||
end
|
||||
if self.parent.dir then
|
||||
local info = '#:' .. Util.size(self.parent.dir.files)
|
||||
local numMarked = Util.size(marked)
|
||||
if numMarked > 0 then
|
||||
info = info .. ' M:' .. numMarked
|
||||
end
|
||||
self:setValue('info', info)
|
||||
self:setValue('totalSize', formatSize(self.parent.dir.totalSize))
|
||||
UI.StatusBar.draw(self)
|
||||
end
|
||||
end
|
||||
|
||||
function Browser:setStatus(status, ...)
|
||||
self.notification:info(string.format(status, ...))
|
||||
self.notification:info(string.format(status, ...))
|
||||
end
|
||||
|
||||
function Browser:unmarkAll()
|
||||
for _,m in pairs(marked) do
|
||||
m.marked = false
|
||||
end
|
||||
Util.clear(marked)
|
||||
for _,m in pairs(marked) do
|
||||
m.marked = false
|
||||
end
|
||||
Util.clear(marked)
|
||||
end
|
||||
|
||||
function Browser:getDirectory(directory)
|
||||
local s, dir = pcall(function()
|
||||
local s, dir = pcall(function()
|
||||
|
||||
local dir = directories[directory]
|
||||
if not dir then
|
||||
dir = {
|
||||
name = directory,
|
||||
size = 0,
|
||||
files = { },
|
||||
totalSize = 0,
|
||||
index = 1
|
||||
}
|
||||
directories[directory] = dir
|
||||
end
|
||||
local dir = directories[directory]
|
||||
if not dir then
|
||||
dir = {
|
||||
name = directory,
|
||||
size = 0,
|
||||
files = { },
|
||||
totalSize = 0,
|
||||
index = 1
|
||||
}
|
||||
directories[directory] = dir
|
||||
end
|
||||
|
||||
self:updateDirectory(dir)
|
||||
self:updateDirectory(dir)
|
||||
|
||||
return dir
|
||||
end)
|
||||
return dir
|
||||
end)
|
||||
|
||||
return s, dir
|
||||
return s, dir
|
||||
end
|
||||
|
||||
function Browser:updateDirectory(dir)
|
||||
|
||||
dir.size = 0
|
||||
dir.totalSize = 0
|
||||
Util.clear(dir.files)
|
||||
dir.size = 0
|
||||
dir.totalSize = 0
|
||||
Util.clear(dir.files)
|
||||
|
||||
local files = fs.listEx(dir.name)
|
||||
if files then
|
||||
dir.size = #files
|
||||
for _, file in pairs(files) do
|
||||
file.fullName = fs.combine(dir.name, file.name)
|
||||
file.flags = ''
|
||||
if not file.isDir then
|
||||
dir.totalSize = dir.totalSize + file.size
|
||||
file.fsize = formatSize(file.size)
|
||||
else
|
||||
if config.showDirSizes then
|
||||
file.size = fs.getSize(file.fullName, true)
|
||||
local files = fs.listEx(dir.name)
|
||||
if files then
|
||||
dir.size = #files
|
||||
for _, file in pairs(files) do
|
||||
file.fullName = fs.combine(dir.name, file.name)
|
||||
file.flags = ''
|
||||
if not file.isDir then
|
||||
dir.totalSize = dir.totalSize + file.size
|
||||
file.fsize = formatSize(file.size)
|
||||
else
|
||||
if config.showDirSizes then
|
||||
file.size = fs.getSize(file.fullName, true)
|
||||
|
||||
dir.totalSize = dir.totalSize + file.size
|
||||
file.fsize = formatSize(file.size)
|
||||
end
|
||||
file.flags = 'D'
|
||||
end
|
||||
if file.isReadOnly then
|
||||
file.flags = file.flags .. 'R'
|
||||
end
|
||||
if config.showHidden or file.name:sub(1, 1) ~= '.' then
|
||||
dir.files[file.fullName] = file
|
||||
end
|
||||
end
|
||||
end
|
||||
dir.totalSize = dir.totalSize + file.size
|
||||
file.fsize = formatSize(file.size)
|
||||
end
|
||||
file.flags = 'D'
|
||||
end
|
||||
if file.isReadOnly then
|
||||
file.flags = file.flags .. 'R'
|
||||
end
|
||||
if config.showHidden or file.name:sub(1, 1) ~= '.' then
|
||||
dir.files[file.fullName] = file
|
||||
end
|
||||
end
|
||||
end
|
||||
-- self.grid:update()
|
||||
-- self.grid:setIndex(dir.index)
|
||||
self.grid:setValues(dir.files)
|
||||
self.grid:setValues(dir.files)
|
||||
end
|
||||
|
||||
function Browser:setDir(dirName, noStatus)
|
||||
self:unmarkAll()
|
||||
self:unmarkAll()
|
||||
|
||||
if self.dir then
|
||||
self.dir.index = self.grid:getIndex()
|
||||
end
|
||||
local DIR = fs.combine('', dirName)
|
||||
shell.setDir(DIR)
|
||||
local s, dir = self:getDirectory(DIR)
|
||||
if s then
|
||||
self.dir = dir
|
||||
elseif noStatus then
|
||||
error(dir)
|
||||
else
|
||||
self:setStatus(dir)
|
||||
self:setDir('', true)
|
||||
return
|
||||
end
|
||||
if self.dir then
|
||||
self.dir.index = self.grid:getIndex()
|
||||
end
|
||||
local DIR = fs.combine('', dirName)
|
||||
shell.setDir(DIR)
|
||||
local s, dir = self:getDirectory(DIR)
|
||||
if s then
|
||||
self.dir = dir
|
||||
elseif noStatus then
|
||||
error(dir)
|
||||
else
|
||||
self:setStatus(dir)
|
||||
self:setDir('', true)
|
||||
return
|
||||
end
|
||||
|
||||
if not noStatus then
|
||||
self.statusBar:setValue('status', '/' .. self.dir.name)
|
||||
self.statusBar:draw()
|
||||
end
|
||||
self.grid:setIndex(self.dir.index)
|
||||
if not noStatus then
|
||||
self.statusBar:setValue('status', '/' .. self.dir.name)
|
||||
self.statusBar:draw()
|
||||
end
|
||||
self.grid:setIndex(self.dir.index)
|
||||
end
|
||||
|
||||
function Browser:run(...)
|
||||
if multishell then
|
||||
local tabId = shell.openTab(...)
|
||||
multishell.setFocus(tabId)
|
||||
else
|
||||
shell.run(...)
|
||||
Event.terminate = false
|
||||
self:draw()
|
||||
end
|
||||
if multishell then
|
||||
local tabId = shell.openTab(...)
|
||||
multishell.setFocus(tabId)
|
||||
else
|
||||
shell.run(...)
|
||||
Event.terminate = false
|
||||
self:draw()
|
||||
end
|
||||
end
|
||||
|
||||
function Browser:hasMarked()
|
||||
if Util.size(marked) == 0 then
|
||||
local file = self.grid:getSelected()
|
||||
if file then
|
||||
file.marked = true
|
||||
marked[file.fullName] = file
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
return Util.size(marked) > 0
|
||||
if Util.size(marked) == 0 then
|
||||
local file = self.grid:getSelected()
|
||||
if file then
|
||||
file.marked = true
|
||||
marked[file.fullName] = file
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
return Util.size(marked) > 0
|
||||
end
|
||||
|
||||
function Browser:eventHandler(event)
|
||||
local file = self.grid:getSelected()
|
||||
local file = self.grid:getSelected()
|
||||
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
|
||||
elseif event.type == 'edit' and file then
|
||||
self:run('edit', file.name)
|
||||
elseif event.type == 'edit' and file then
|
||||
self:run('edit', file.name)
|
||||
|
||||
elseif event.type == 'cedit' and file then
|
||||
self:run('cedit', file.name)
|
||||
self:setStatus('Started cloud edit')
|
||||
elseif event.type == 'cedit' and file then
|
||||
self:run('cedit', file.name)
|
||||
self:setStatus('Started cloud edit')
|
||||
|
||||
elseif event.type == 'shell' then
|
||||
self:run('sys/apps/shell.lua')
|
||||
elseif event.type == 'shell' then
|
||||
self:run('sys/apps/shell.lua')
|
||||
|
||||
elseif event.type == 'refresh' then
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
self:setStatus('Refreshed')
|
||||
elseif event.type == 'refresh' then
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
self:setStatus('Refreshed')
|
||||
|
||||
elseif event.type == 'associate' then
|
||||
self.associations:show()
|
||||
elseif event.type == 'associate' then
|
||||
self.associations:show()
|
||||
|
||||
elseif event.type == 'pastebin' then
|
||||
if file and not file.isDir then
|
||||
local s, m = pastebin.put(file.fullName)
|
||||
if s then
|
||||
os.queueEvent('clipboard_copy', s)
|
||||
self.notification:success(string.format('Uploaded as %s', s), 0)
|
||||
else
|
||||
self.notification:error(m)
|
||||
end
|
||||
end
|
||||
elseif event.type == 'pastebin' then
|
||||
if file and not file.isDir then
|
||||
local s, m = pastebin.put(file.fullName)
|
||||
if s then
|
||||
os.queueEvent('clipboard_copy', s)
|
||||
self.notification:success(string.format('Uploaded as %s', s), 0)
|
||||
else
|
||||
self.notification:error(m)
|
||||
end
|
||||
end
|
||||
|
||||
elseif event.type == 'toggle_hidden' then
|
||||
config.showHidden = not config.showHidden
|
||||
Config.update('Files', config)
|
||||
elseif event.type == 'toggle_hidden' then
|
||||
config.showHidden = not config.showHidden
|
||||
Config.update('Files', config)
|
||||
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
if not config.showHidden then
|
||||
self:setStatus('Hiding hidden')
|
||||
else
|
||||
self:setStatus('Displaying hidden')
|
||||
end
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
if not config.showHidden then
|
||||
self:setStatus('Hiding hidden')
|
||||
else
|
||||
self:setStatus('Displaying hidden')
|
||||
end
|
||||
|
||||
elseif event.type == 'toggle_dirSize' then
|
||||
config.showDirSizes = not config.showDirSizes
|
||||
Config.update('Files', config)
|
||||
elseif event.type == 'toggle_dirSize' then
|
||||
config.showDirSizes = not config.showDirSizes
|
||||
Config.update('Files', config)
|
||||
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
if config.showDirSizes then
|
||||
self:setStatus('Displaying dir sizes')
|
||||
end
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
if config.showDirSizes then
|
||||
self:setStatus('Displaying dir sizes')
|
||||
end
|
||||
|
||||
elseif event.type == 'mark' and file then
|
||||
file.marked = not file.marked
|
||||
if file.marked then
|
||||
marked[file.fullName] = file
|
||||
else
|
||||
marked[file.fullName] = nil
|
||||
end
|
||||
self.grid:draw()
|
||||
self.statusBar:draw()
|
||||
elseif event.type == 'mark' and file then
|
||||
file.marked = not file.marked
|
||||
if file.marked then
|
||||
marked[file.fullName] = file
|
||||
else
|
||||
marked[file.fullName] = nil
|
||||
end
|
||||
self.grid:draw()
|
||||
self.statusBar:draw()
|
||||
|
||||
elseif event.type == 'unmark' then
|
||||
self:unmarkAll()
|
||||
self.grid:draw()
|
||||
self:setStatus('Marked files cleared')
|
||||
elseif event.type == 'unmark' then
|
||||
self:unmarkAll()
|
||||
self.grid:draw()
|
||||
self:setStatus('Marked files cleared')
|
||||
|
||||
elseif event.type == 'grid_select' or event.type == 'run' then
|
||||
if file then
|
||||
if file.isDir then
|
||||
self:setDir(file.fullName)
|
||||
else
|
||||
local ext = file.name:match('%.(%w+)$')
|
||||
if ext and config.associations[ext] then
|
||||
self:run(config.associations[ext], '/' .. file.fullName)
|
||||
else
|
||||
self:run(file.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif event.type == 'grid_select' or event.type == 'run' then
|
||||
if file then
|
||||
if file.isDir then
|
||||
self:setDir(file.fullName)
|
||||
else
|
||||
local ext = file.name:match('%.(%w+)$')
|
||||
if ext and config.associations[ext] then
|
||||
self:run(config.associations[ext], '/' .. file.fullName)
|
||||
else
|
||||
self:run(file.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif event.type == 'updir' then
|
||||
local dir = (self.dir.name:match("(.*/)"))
|
||||
self:setDir(dir or '/')
|
||||
elseif event.type == 'updir' then
|
||||
local dir = (self.dir.name:match("(.*/)"))
|
||||
self:setDir(dir or '/')
|
||||
|
||||
elseif event.type == 'delete' then
|
||||
if self:hasMarked() then
|
||||
local width = self.statusBar:getColumnWidth('status')
|
||||
self.statusBar:setColumnWidth('status', UI.term.width)
|
||||
self.statusBar:setValue('status', 'Delete marked? (y/n)')
|
||||
self.statusBar:draw()
|
||||
self.statusBar:sync()
|
||||
local _, ch = os.pullEvent('char')
|
||||
if ch == 'y' or ch == 'Y' then
|
||||
for _,m in pairs(marked) do
|
||||
pcall(function()
|
||||
fs.delete(m.fullName)
|
||||
end)
|
||||
end
|
||||
end
|
||||
marked = { }
|
||||
self.statusBar:setColumnWidth('status', width)
|
||||
self.statusBar:setValue('status', '/' .. self.dir.name)
|
||||
self:updateDirectory(self.dir)
|
||||
elseif event.type == 'delete' then
|
||||
if self:hasMarked() then
|
||||
local width = self.statusBar:getColumnWidth('status')
|
||||
self.statusBar:setColumnWidth('status', UI.term.width)
|
||||
self.statusBar:setValue('status', 'Delete marked? (y/n)')
|
||||
self.statusBar:draw()
|
||||
self.statusBar:sync()
|
||||
local _, ch = os.pullEvent('char')
|
||||
if ch == 'y' or ch == 'Y' then
|
||||
for _,m in pairs(marked) do
|
||||
pcall(function()
|
||||
fs.delete(m.fullName)
|
||||
end)
|
||||
end
|
||||
end
|
||||
marked = { }
|
||||
self.statusBar:setColumnWidth('status', width)
|
||||
self.statusBar:setValue('status', '/' .. self.dir.name)
|
||||
self:updateDirectory(self.dir)
|
||||
|
||||
self.statusBar:draw()
|
||||
self.grid:draw()
|
||||
self:setFocus(self.grid)
|
||||
end
|
||||
self.statusBar:draw()
|
||||
self.grid:draw()
|
||||
self:setFocus(self.grid)
|
||||
end
|
||||
|
||||
elseif event.type == 'copy' or event.type == 'cut' then
|
||||
if self:hasMarked() then
|
||||
cutMode = event.type == 'cut'
|
||||
Util.clear(copied)
|
||||
Util.merge(copied, marked)
|
||||
--self:unmarkAll()
|
||||
self.grid:draw()
|
||||
self:setStatus('Copied %d file(s)', Util.size(copied))
|
||||
end
|
||||
elseif event.type == 'copy' or event.type == 'cut' then
|
||||
if self:hasMarked() then
|
||||
cutMode = event.type == 'cut'
|
||||
Util.clear(copied)
|
||||
Util.merge(copied, marked)
|
||||
--self:unmarkAll()
|
||||
self.grid:draw()
|
||||
self:setStatus('Copied %d file(s)', Util.size(copied))
|
||||
end
|
||||
|
||||
elseif event.type == 'copy_path' then
|
||||
if file then
|
||||
os.queueEvent('clipboard_copy', file.fullName)
|
||||
end
|
||||
elseif event.type == 'copy_path' then
|
||||
if file then
|
||||
os.queueEvent('clipboard_copy', file.fullName)
|
||||
end
|
||||
|
||||
elseif event.type == 'paste' then
|
||||
for _,m in pairs(copied) do
|
||||
local s, m = pcall(function()
|
||||
if cutMode then
|
||||
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
|
||||
else
|
||||
fs.copy(m.fullName, fs.combine(self.dir.name, m.name))
|
||||
end
|
||||
end)
|
||||
end
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
self:setStatus('Pasted ' .. Util.size(copied) .. ' file(s)')
|
||||
elseif event.type == 'paste' then
|
||||
for _,m in pairs(copied) do
|
||||
local s, m = pcall(function()
|
||||
if cutMode then
|
||||
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
|
||||
else
|
||||
fs.copy(m.fullName, fs.combine(self.dir.name, m.name))
|
||||
end
|
||||
end)
|
||||
end
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
self:setStatus('Pasted ' .. Util.size(copied) .. ' file(s)')
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
self:setFocus(self.grid)
|
||||
return true
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
self:setFocus(self.grid)
|
||||
return true
|
||||
end
|
||||
|
||||
--[[ Associations slide out ]] --
|
||||
function Browser.associations:show()
|
||||
self.grid.values = { }
|
||||
for k, v in pairs(config.associations) do
|
||||
table.insert(self.grid.values, {
|
||||
name = k,
|
||||
value = v,
|
||||
})
|
||||
end
|
||||
self.grid:update()
|
||||
UI.SlideOut.show(self)
|
||||
self:setFocus(self.form[1])
|
||||
self.grid.values = { }
|
||||
for k, v in pairs(config.associations) do
|
||||
table.insert(self.grid.values, {
|
||||
name = k,
|
||||
value = v,
|
||||
})
|
||||
end
|
||||
self.grid:update()
|
||||
UI.SlideOut.show(self)
|
||||
self:setFocus(self.form[1])
|
||||
end
|
||||
|
||||
function Browser.associations:eventHandler(event)
|
||||
if event.type == 'remove_entry' then
|
||||
local row = self.grid:getSelected()
|
||||
if row then
|
||||
Util.removeByValue(self.grid.values, row)
|
||||
self.grid:update()
|
||||
self.grid:draw()
|
||||
end
|
||||
if event.type == 'remove_entry' then
|
||||
local row = self.grid:getSelected()
|
||||
if row then
|
||||
Util.removeByValue(self.grid.values, row)
|
||||
self.grid:update()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'add_association' then
|
||||
if self.form:save() then
|
||||
local entry = Util.find(self.grid.values, 'name', self.form[1].value) or { }
|
||||
entry.name = self.form[1].value
|
||||
entry.value = self.form[2].value
|
||||
table.insert(self.grid.values, entry)
|
||||
self.form[1]:reset()
|
||||
self.form[2]:reset()
|
||||
self.grid:update()
|
||||
self.grid:draw()
|
||||
end
|
||||
elseif event.type == 'add_association' then
|
||||
if self.form:save() then
|
||||
local entry = Util.find(self.grid.values, 'name', self.form[1].value) or { }
|
||||
entry.name = self.form[1].value
|
||||
entry.value = self.form[2].value
|
||||
table.insert(self.grid.values, entry)
|
||||
self.form[1]:reset()
|
||||
self.form[2]:reset()
|
||||
self.grid:update()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'cancel' then
|
||||
self:hide()
|
||||
elseif event.type == 'cancel' then
|
||||
self:hide()
|
||||
|
||||
elseif event.type == 'save' then
|
||||
config.associations = { }
|
||||
for _, v in pairs(self.grid.values) do
|
||||
config.associations[v.name] = v.value
|
||||
end
|
||||
Config.update('Files', config)
|
||||
self:hide()
|
||||
elseif event.type == 'save' then
|
||||
config.associations = { }
|
||||
for _, v in pairs(self.grid.values) do
|
||||
config.associations[v.name] = v.value
|
||||
end
|
||||
Config.update('Files', config)
|
||||
self:hide()
|
||||
|
||||
else
|
||||
return UI.SlideOut.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
else
|
||||
return UI.SlideOut.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--[[-- Startup logic --]]--
|
||||
|
@ -294,7 +294,6 @@ end
|
||||
function page:rawExecute(s)
|
||||
local fn, m
|
||||
local wrapped
|
||||
local t = os.clock()
|
||||
|
||||
fn = load('return (' ..s.. ')', 'lua', nil, sandboxEnv)
|
||||
|
||||
@ -303,6 +302,7 @@ function page:rawExecute(s)
|
||||
wrapped = true
|
||||
end
|
||||
|
||||
local t = os.clock()
|
||||
if fn then
|
||||
fn, m = pcall(fn)
|
||||
if #m <= 1 and wrapped then
|
||||
@ -311,19 +311,24 @@ function page:rawExecute(s)
|
||||
else
|
||||
fn, m = load(s, 'lua', nil, sandboxEnv)
|
||||
if fn then
|
||||
t = os.clock()
|
||||
fn, m = pcall(fn)
|
||||
end
|
||||
end
|
||||
|
||||
if fn then
|
||||
t = os.clock() - t
|
||||
|
||||
local bg, fg = term.getBackgroundColor(), term.getTextColor()
|
||||
term.setTextColor(colors.cyan)
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.write(string.format('out [%.2f]: ', t))
|
||||
term.setBackgroundColor(bg)
|
||||
term.setTextColor(fg)
|
||||
if m or wrapped then
|
||||
local bg, fg = term.getBackgroundColor(), term.getTextColor()
|
||||
term.setTextColor(colors.cyan)
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.write(string.format('out [%.2f]: ', os.clock() - t))
|
||||
term.setBackgroundColor(bg)
|
||||
term.setTextColor(fg)
|
||||
Util.print(m or 'nil')
|
||||
else
|
||||
print()
|
||||
end
|
||||
else
|
||||
_G.printError(m)
|
||||
|
@ -127,12 +127,14 @@ local function sendCommand(host, command)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
function page.ports:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
shell.openForegroundTab('sniff ' .. event.selected.port)
|
||||
end
|
||||
return UI.SlideOut.eventHandler(self, event)
|
||||
end
|
||||
]]
|
||||
|
||||
function page.ports.grid:update()
|
||||
local function findConnection(port)
|
||||
|
@ -5,23 +5,23 @@ local shell = _ENV.shell
|
||||
local launcherTab = kernel.getCurrent()
|
||||
|
||||
kernel.hook('kernel_focus', function(_, eventData)
|
||||
local focusTab = eventData and eventData[1]
|
||||
if focusTab == launcherTab.uid then
|
||||
local previousTab = eventData[2]
|
||||
local nextTab = launcherTab
|
||||
if not previousTab then
|
||||
for _, v in pairs(kernel.routines) do
|
||||
if not v.hidden and v.uid > nextTab.uid then
|
||||
nextTab = v
|
||||
end
|
||||
end
|
||||
end
|
||||
if nextTab == launcherTab then
|
||||
shell.switchTab(shell.openTab('sys/apps/shell.lua'))
|
||||
else
|
||||
shell.switchTab(nextTab.uid)
|
||||
end
|
||||
end
|
||||
local focusTab = eventData and eventData[1]
|
||||
if focusTab == launcherTab.uid then
|
||||
local previousTab = eventData[2]
|
||||
local nextTab = launcherTab
|
||||
if not previousTab then
|
||||
for _, v in pairs(kernel.routines) do
|
||||
if not v.hidden and v.uid > nextTab.uid then
|
||||
nextTab = v
|
||||
end
|
||||
end
|
||||
end
|
||||
if nextTab == launcherTab then
|
||||
shell.switchTab(shell.openTab('sys/apps/shell.lua'))
|
||||
else
|
||||
shell.switchTab(nextTab.uid)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
while os.pullEventRaw() do end
|
||||
os.pullEventRaw('kernel_halt')
|
@ -23,115 +23,115 @@ local packagesIntro = [[Setup Complete
|
||||
|
||||
local page = UI.Page {
|
||||
wizard = UI.Wizard {
|
||||
ey = -2,
|
||||
ey = -2,
|
||||
pages = {
|
||||
splash = UI.WizardPage {
|
||||
index = 1,
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 2, ey = -2,
|
||||
value = string.format(splashIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
index = 1,
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 2, ey = -2,
|
||||
value = string.format(splashIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
label = UI.WizardPage {
|
||||
index = 2,
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Label'
|
||||
},
|
||||
label = UI.TextEntry {
|
||||
x = 9, y = 2, ex = -3,
|
||||
limit = 32,
|
||||
value = os.getComputerLabel(),
|
||||
},
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 4, ey = -3,
|
||||
value = string.format(labelIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
index = 2,
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Label'
|
||||
},
|
||||
label = UI.TextEntry {
|
||||
x = 9, y = 2, ex = -3,
|
||||
limit = 32,
|
||||
value = os.getComputerLabel(),
|
||||
},
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 4, ey = -3,
|
||||
value = string.format(labelIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
password = UI.WizardPage {
|
||||
index = 3,
|
||||
passwordLabel = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Password'
|
||||
},
|
||||
newPass = UI.TextEntry {
|
||||
x = 12, ex = -3, y = 2,
|
||||
limit = 32,
|
||||
mask = true,
|
||||
shadowText = 'password',
|
||||
},
|
||||
passwordLabel = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Password'
|
||||
},
|
||||
newPass = UI.TextEntry {
|
||||
x = 12, ex = -3, y = 2,
|
||||
limit = 32,
|
||||
mask = true,
|
||||
shadowText = 'password',
|
||||
},
|
||||
--[[
|
||||
groupLabel = UI.Text {
|
||||
x = 3, y = 3,
|
||||
value = 'Group'
|
||||
},
|
||||
group = UI.TextEntry {
|
||||
x = 12, ex = -3, y = 3,
|
||||
limit = 32,
|
||||
shadowText = 'network group',
|
||||
},
|
||||
groupLabel = UI.Text {
|
||||
x = 3, y = 3,
|
||||
value = 'Group'
|
||||
},
|
||||
group = UI.TextEntry {
|
||||
x = 12, ex = -3, y = 3,
|
||||
limit = 32,
|
||||
shadowText = 'network group',
|
||||
},
|
||||
]]
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 5, ey = -3,
|
||||
value = string.format(passwordIntro, Ansi.white),
|
||||
},
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 5, ey = -3,
|
||||
value = string.format(passwordIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
packages = UI.WizardPage {
|
||||
index = 4,
|
||||
button = UI.Button {
|
||||
x = 3, y = -3,
|
||||
text = 'Open Package Manager',
|
||||
event = 'packages',
|
||||
},
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 2, ey = -4,
|
||||
value = string.format(packagesIntro, Ansi.white),
|
||||
},
|
||||
button = UI.Button {
|
||||
x = 3, y = -3,
|
||||
text = 'Open Package Manager',
|
||||
event = 'packages',
|
||||
},
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 2, ey = -4,
|
||||
value = string.format(packagesIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
}
|
||||
|
||||
function page.wizard.pages.label:validate()
|
||||
os.setComputerLabel(self.label.value)
|
||||
return true
|
||||
os.setComputerLabel(self.label.value)
|
||||
return true
|
||||
end
|
||||
|
||||
function page.wizard.pages.password:validate()
|
||||
if #self.newPass.value > 0 then
|
||||
Security.updatePassword(SHA1.sha1(self.newPass.value))
|
||||
end
|
||||
--[[
|
||||
if #self.group.value > 0 then
|
||||
local config = Config.load('os')
|
||||
config.group = self.group.value
|
||||
Config.update('os', config)
|
||||
end
|
||||
]]
|
||||
return true
|
||||
if #self.newPass.value > 0 then
|
||||
Security.updatePassword(SHA1.sha1(self.newPass.value))
|
||||
end
|
||||
--[[
|
||||
if #self.group.value > 0 then
|
||||
local config = Config.load('os')
|
||||
config.group = self.group.value
|
||||
Config.update('os', config)
|
||||
end
|
||||
]]
|
||||
return true
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'skip' then
|
||||
self.wizard:emit({ type = 'nextView' })
|
||||
if event.type == 'skip' then
|
||||
self.wizard:emit({ type = 'nextView' })
|
||||
|
||||
elseif event.type == 'view_enabled' then
|
||||
event.view:focusFirst()
|
||||
elseif event.type == 'view_enabled' then
|
||||
event.view:focusFirst()
|
||||
|
||||
elseif event.type == 'packages' then
|
||||
shell.openForegroundTab('PackageManager')
|
||||
elseif event.type == 'packages' then
|
||||
shell.openForegroundTab('PackageManager')
|
||||
|
||||
elseif event.type == 'wizard_complete' or event.type == 'cancel' then
|
||||
UI.exitPullEvents()
|
||||
elseif event.type == 'wizard_complete' or event.type == 'cancel' then
|
||||
UI.exitPullEvents()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
|
@ -11,60 +11,60 @@ local term = _G.term
|
||||
local success = true
|
||||
|
||||
local function runDir(directory)
|
||||
if not fs.exists(directory) then
|
||||
return true
|
||||
end
|
||||
if not fs.exists(directory) then
|
||||
return true
|
||||
end
|
||||
|
||||
local files = fs.list(directory)
|
||||
table.sort(files)
|
||||
local files = fs.list(directory)
|
||||
table.sort(files)
|
||||
|
||||
for _,file in ipairs(files) do
|
||||
os.sleep(0)
|
||||
local result, err = shell.run(directory .. '/' .. file)
|
||||
for _,file in ipairs(files) do
|
||||
os.sleep(0)
|
||||
local result, err = shell.run(directory .. '/' .. file)
|
||||
|
||||
if result then
|
||||
if term.isColor() then
|
||||
term.setTextColor(colors.green)
|
||||
end
|
||||
term.write('[PASS] ')
|
||||
term.setTextColor(colors.white)
|
||||
term.write(fs.combine(directory, file))
|
||||
print()
|
||||
else
|
||||
if term.isColor() then
|
||||
term.setTextColor(colors.red)
|
||||
end
|
||||
term.write('[FAIL] ')
|
||||
term.setTextColor(colors.white)
|
||||
term.write(fs.combine(directory, file))
|
||||
if err then
|
||||
_G.printError('\n' .. err)
|
||||
end
|
||||
print()
|
||||
success = false
|
||||
end
|
||||
end
|
||||
if result then
|
||||
if term.isColor() then
|
||||
term.setTextColor(colors.green)
|
||||
end
|
||||
term.write('[PASS] ')
|
||||
term.setTextColor(colors.white)
|
||||
term.write(fs.combine(directory, file))
|
||||
print()
|
||||
else
|
||||
if term.isColor() then
|
||||
term.setTextColor(colors.red)
|
||||
end
|
||||
term.write('[FAIL] ')
|
||||
term.setTextColor(colors.white)
|
||||
term.write(fs.combine(directory, file))
|
||||
if err then
|
||||
_G.printError('\n' .. err)
|
||||
end
|
||||
print()
|
||||
success = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
runDir('sys/autorun')
|
||||
for name in pairs(Packages:installed()) do
|
||||
local packageDir = 'packages/' .. name .. '/autorun'
|
||||
runDir(packageDir)
|
||||
local packageDir = 'packages/' .. name .. '/autorun'
|
||||
runDir(packageDir)
|
||||
end
|
||||
runDir('usr/autorun')
|
||||
|
||||
if not success then
|
||||
if multishell then
|
||||
multishell.setFocus(multishell.getCurrent())
|
||||
end
|
||||
_G.printError('A startup program has errored')
|
||||
print('Press enter to continue')
|
||||
if multishell then
|
||||
multishell.setFocus(multishell.getCurrent())
|
||||
end
|
||||
_G.printError('A startup program has errored')
|
||||
print('Press enter to continue')
|
||||
|
||||
while true do
|
||||
local e, code = os.pullEventRaw('key')
|
||||
if e == 'terminate' or e == 'key' and code == keys.enter then
|
||||
break
|
||||
end
|
||||
end
|
||||
while true do
|
||||
local e, code = os.pullEventRaw('key')
|
||||
if e == 'terminate' or e == 'key' and code == keys.enter then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -7,32 +7,32 @@ local shell = _ENV.shell
|
||||
|
||||
local args = { ... }
|
||||
if not args[1] then
|
||||
error('Syntax: cedit <filename>')
|
||||
error('Syntax: cedit <filename>')
|
||||
end
|
||||
|
||||
if not _G.http.websocket then
|
||||
error('Requires CC: Tweaked')
|
||||
error('Requires CC: Tweaked')
|
||||
end
|
||||
|
||||
if not _G.cloud_catcher then
|
||||
local key = Config.load('cloud').key
|
||||
local key = Config.load('cloud').key
|
||||
|
||||
if not key then
|
||||
print('Visit https://cloud-catcher.squiddev.cc')
|
||||
print('Paste key: ')
|
||||
key = read()
|
||||
if #key == 0 then
|
||||
return
|
||||
end
|
||||
end
|
||||
if not key then
|
||||
print('Visit https://cloud-catcher.squiddev.cc')
|
||||
print('Paste key: ')
|
||||
key = read()
|
||||
if #key == 0 then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- open an unfocused tab
|
||||
local id = shell.openTab('cloud ' .. key)
|
||||
print('Connecting...')
|
||||
while not _G.cloud_catcher do
|
||||
os.sleep(.2)
|
||||
end
|
||||
multishell.setTitle(id, 'Cloud')
|
||||
-- open an unfocused tab
|
||||
local id = shell.openTab('cloud ' .. key)
|
||||
print('Connecting...')
|
||||
while not _G.cloud_catcher do
|
||||
os.sleep(.2)
|
||||
end
|
||||
multishell.setTitle(id, 'Cloud')
|
||||
end
|
||||
|
||||
shell.run('cloud edit ' .. table.unpack({ ... }))
|
||||
|
@ -4,20 +4,20 @@ local read = _G.read
|
||||
local shell = _ENV.shell
|
||||
|
||||
if not _G.http.websocket then
|
||||
error('Requires CC: Tweaked')
|
||||
error('Requires CC: Tweaked')
|
||||
end
|
||||
|
||||
if not _G.cloud_catcher then
|
||||
local key = Config.load('cloud').key
|
||||
local key = Config.load('cloud').key
|
||||
|
||||
if not key then
|
||||
print('Visit https://cloud-catcher.squiddev.cc')
|
||||
print('Paste key: ')
|
||||
key = read()
|
||||
if #key == 0 then
|
||||
return
|
||||
end
|
||||
end
|
||||
print('Connecting...')
|
||||
shell.run('cloud ' .. key)
|
||||
if not key then
|
||||
print('Visit https://cloud-catcher.squiddev.cc')
|
||||
print('Paste key: ')
|
||||
key = read()
|
||||
if #key == 0 then
|
||||
return
|
||||
end
|
||||
end
|
||||
print('Connecting...')
|
||||
shell.run('cloud ' .. key)
|
||||
end
|
||||
|
@ -1,115 +0,0 @@
|
||||
local Event = require('event')
|
||||
local Util = require('util')
|
||||
|
||||
local fs = _G.fs
|
||||
local modem = _G.device.wireless_modem
|
||||
local os = _G.os
|
||||
|
||||
local computerId = os.getComputerID()
|
||||
|
||||
--modem.open(80)
|
||||
|
||||
-- https://github.com/golgote/neturl/blob/master/lib/net/url.lua
|
||||
local function parseQuery(str)
|
||||
local sep = '&'
|
||||
|
||||
local values = {}
|
||||
for key,val in str:gmatch(string.format('([^%q=]+)(=*[^%q=]*)', sep, sep)) do
|
||||
--local key = decode(key)
|
||||
local keys = {}
|
||||
key = key:gsub('%[([^%]]*)%]', function(v)
|
||||
-- extract keys between balanced brackets
|
||||
if string.find(v, "^-?%d+$") then
|
||||
v = tonumber(v)
|
||||
--else
|
||||
--v = decode(v)
|
||||
end
|
||||
table.insert(keys, v)
|
||||
return "="
|
||||
end)
|
||||
key = key:gsub('=+.*$', "")
|
||||
key = key:gsub('%s', "_") -- remove spaces in parameter name
|
||||
val = val:gsub('^=+', "")
|
||||
|
||||
if not values[key] then
|
||||
values[key] = {}
|
||||
end
|
||||
if #keys > 0 and type(values[key]) ~= 'table' then
|
||||
values[key] = {}
|
||||
elseif #keys == 0 and type(values[key]) == 'table' then
|
||||
values[key] = val --decode(val)
|
||||
end
|
||||
|
||||
local t = values[key]
|
||||
for i,k in ipairs(keys) do
|
||||
if type(t) ~= 'table' then
|
||||
t = {}
|
||||
end
|
||||
if k == "" then
|
||||
k = #t+1
|
||||
end
|
||||
if not t[k] then
|
||||
t[k] = {}
|
||||
end
|
||||
if i == #keys then
|
||||
t[k] = val --decode(val)
|
||||
end
|
||||
t = t[k]
|
||||
end
|
||||
end
|
||||
return values
|
||||
end
|
||||
|
||||
local function getListing(path, recursive)
|
||||
local list = { }
|
||||
local function listing(p)
|
||||
for _, f in pairs(fs.listEx(p)) do
|
||||
local abs = fs.combine(p, f.name)
|
||||
table.insert(list, {
|
||||
isDir = f.isDir,
|
||||
path = string.sub(abs, #path + 1),
|
||||
size = f.size,
|
||||
})
|
||||
if recursive and f.isDir then
|
||||
listing(abs)
|
||||
end
|
||||
end
|
||||
end
|
||||
listing(path)
|
||||
return list
|
||||
end
|
||||
|
||||
--[[
|
||||
Event.on('modem_message', function(_, _, dport, dhost, request)
|
||||
if dport == 80 and dhost == computerId and type(request) == 'table' then
|
||||
if request.method == 'GET' then
|
||||
local query
|
||||
if not request.path or type(request.path) ~= 'string' then
|
||||
return
|
||||
end
|
||||
local path = request.path:gsub('%?(.*)', function(v)
|
||||
query = parseQuery(v)
|
||||
return ''
|
||||
end)
|
||||
if fs.isDir(path) then
|
||||
-- TODO: more validation
|
||||
modem.transmit(request.replyPort, request.replyAddress, {
|
||||
statusCode = 200,
|
||||
contentType = 'table/directory',
|
||||
data = getListing(path, query and query.recursive == 'true'),
|
||||
})
|
||||
elseif fs.exists(path) then
|
||||
modem.transmit(request.replyPort, request.replyAddress, {
|
||||
statusCode = 200,
|
||||
contentType = 'table/file',
|
||||
data = Util.readFile(path),
|
||||
})
|
||||
else
|
||||
modem.transmit(request.replyPort, request.replyAddress, {
|
||||
statusCode = 404,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
]]
|
@ -156,7 +156,7 @@ local function sendInfo()
|
||||
info.label = os.getComputerLabel()
|
||||
info.uptime = math.floor(os.clock())
|
||||
info.group = network.getGroup()
|
||||
if turtle then
|
||||
if turtle and turtle.getStatus then
|
||||
info.fuel = turtle.getFuelLevel()
|
||||
info.status = turtle.getStatus()
|
||||
info.point = turtle.point
|
||||
|
@ -1,69 +0,0 @@
|
||||
local Event = require('event')
|
||||
local Terminal = require('terminal')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
local modem = _G.device.wireless_modem
|
||||
local term = _G.term
|
||||
local textutils = _G.textutils
|
||||
|
||||
local terminal = Terminal.window(term.current())
|
||||
terminal.setMaxScroll(300)
|
||||
local oldTerm = term.redirect(terminal)
|
||||
|
||||
local function syntax()
|
||||
error('Syntax: sniff [port]')
|
||||
end
|
||||
|
||||
local port = ({ ... })[1] or syntax()
|
||||
port = tonumber(port) or syntax()
|
||||
|
||||
Event.on('modem_message',
|
||||
function(_, _, dport, _, data, _)
|
||||
if dport == port then
|
||||
terminal.scrollBottom()
|
||||
terminal.setTextColor(colors.white)
|
||||
print(textutils.serialize(data))
|
||||
end
|
||||
end)
|
||||
|
||||
Event.on('mouse_scroll', function(_, direction)
|
||||
if direction == -1 then
|
||||
terminal.scrollUp()
|
||||
else
|
||||
terminal.scrollDown()
|
||||
end
|
||||
end)
|
||||
|
||||
local function sniffer(_, _, data)
|
||||
terminal.scrollBottom()
|
||||
terminal.setTextColor(colors.yellow)
|
||||
local ot = term.redirect(terminal)
|
||||
print(textutils.serialize(data))
|
||||
term.redirect(ot)
|
||||
end
|
||||
|
||||
local socket = _G.transport.sockets[port]
|
||||
if socket then
|
||||
if not socket.sniffers then
|
||||
socket.sniffers = { modem.transmit }
|
||||
socket.transmit = function(...)
|
||||
for _,v in pairs(socket.sniffers) do
|
||||
v(...)
|
||||
end
|
||||
end
|
||||
end
|
||||
table.insert(socket.sniffers, sniffer)
|
||||
end
|
||||
|
||||
local s, m = pcall(Event.pullEvents)
|
||||
|
||||
if socket then
|
||||
Util.removeByValue(socket.sniffers, sniffer)
|
||||
end
|
||||
|
||||
term.redirect(oldTerm)
|
||||
|
||||
if not s and m then
|
||||
error(m)
|
||||
end
|
@ -7,51 +7,51 @@ local colors = _G.colors
|
||||
-- -t80x30
|
||||
|
||||
if _G.http.websocket then
|
||||
local config = Config.load('cloud')
|
||||
local config = Config.load('cloud')
|
||||
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Cloud',
|
||||
description = 'Cloud catcher options',
|
||||
key = UI.TextEntry {
|
||||
x = 3, ex = -3, y = 2,
|
||||
limit = 32,
|
||||
value = config.key,
|
||||
shadowText = 'Cloud key',
|
||||
accelerators = {
|
||||
enter = 'update_key',
|
||||
},
|
||||
},
|
||||
button = UI.Button {
|
||||
x = 3, y = 4,
|
||||
text = 'Update',
|
||||
event = 'update_key',
|
||||
},
|
||||
labelText = UI.TextArea {
|
||||
x = 3, ex = -3, y = 6,
|
||||
textColor = colors.yellow,
|
||||
marginLeft = 0, marginRight = 0,
|
||||
value = string.format(
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Cloud',
|
||||
description = 'Cloud catcher options',
|
||||
key = UI.TextEntry {
|
||||
x = 3, ex = -3, y = 2,
|
||||
limit = 32,
|
||||
value = config.key,
|
||||
shadowText = 'Cloud key',
|
||||
accelerators = {
|
||||
enter = 'update_key',
|
||||
},
|
||||
},
|
||||
button = UI.Button {
|
||||
x = 3, y = 4,
|
||||
text = 'Update',
|
||||
event = 'update_key',
|
||||
},
|
||||
labelText = UI.TextArea {
|
||||
x = 3, ex = -3, y = 6,
|
||||
textColor = colors.yellow,
|
||||
marginLeft = 0, marginRight = 0,
|
||||
value = string.format(
|
||||
[[Use a non-changing cloud key. Note that only a single computer can use this session at one time.
|
||||
To obtain a key, visit:
|
||||
%shttps://cloud-catcher.squiddev.cc%s then bookmark:
|
||||
%shttps://cloud-catcher.squiddev.cc/?id=KEY
|
||||
]],
|
||||
Ansi.white, Ansi.reset, Ansi.white),
|
||||
},
|
||||
}
|
||||
]],
|
||||
Ansi.white, Ansi.reset, Ansi.white),
|
||||
},
|
||||
}
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'update_key' then
|
||||
if #self.key.value > 0 then
|
||||
config.key = self.key.value
|
||||
else
|
||||
config.key = nil
|
||||
end
|
||||
Config.update('cloud', config)
|
||||
self:emit({ type = 'success_message', message = 'Updated' })
|
||||
end
|
||||
end
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'update_key' then
|
||||
if #self.key.value > 0 then
|
||||
config.key = self.key.value
|
||||
else
|
||||
config.key = nil
|
||||
end
|
||||
Config.update('cloud', config)
|
||||
self:emit({ type = 'success_message', message = 'Updated' })
|
||||
end
|
||||
end
|
||||
|
||||
return tab
|
||||
return tab
|
||||
end
|
||||
|
||||
|
@ -14,13 +14,13 @@ local tab = UI.Tab {
|
||||
formLabel = 'Monitor', formKey = 'monitor',
|
||||
},
|
||||
textScale = UI.Chooser {
|
||||
formLabel = 'Font Size', formKey = 'textScale',
|
||||
nochoice = 'Small',
|
||||
choices = {
|
||||
{ name = 'Small', value = '.5' },
|
||||
{ name = 'Large', value = '1' },
|
||||
},
|
||||
help = 'Adjust text scaling',
|
||||
formLabel = 'Font Size', formKey = 'textScale',
|
||||
nochoice = 'Small',
|
||||
choices = {
|
||||
{ name = 'Small', value = '.5' },
|
||||
{ name = 'Large', value = '1' },
|
||||
},
|
||||
help = 'Adjust text scaling',
|
||||
},
|
||||
labelText = UI.TextArea {
|
||||
x = 2, ex = -2, y = 5,
|
||||
|
@ -9,76 +9,76 @@ local config = Config.load('multishell')
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Launcher',
|
||||
description = 'Set the application launcher',
|
||||
launcherLabel = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Launcher',
|
||||
},
|
||||
launcher = UI.Chooser {
|
||||
x = 13, y = 2, width = 12,
|
||||
choices = {
|
||||
{ name = 'Overview', value = 'sys/apps/Overview.lua' },
|
||||
{ name = 'Shell', value = 'sys/apps/ShellLauncher.lua' },
|
||||
{ name = 'Custom', value = 'custom' },
|
||||
},
|
||||
},
|
||||
custom = UI.TextEntry {
|
||||
x = 13, ex = -3, y = 3,
|
||||
limit = 128,
|
||||
shadowText = 'File name',
|
||||
},
|
||||
button = UI.Button {
|
||||
x = 3, y = 5,
|
||||
text = 'Update',
|
||||
event = 'update',
|
||||
},
|
||||
labelText = UI.TextArea {
|
||||
x = 3, ex = -3, y = 7,
|
||||
textColor = colors.yellow,
|
||||
value = 'Choose an application launcher',
|
||||
},
|
||||
launcherLabel = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Launcher',
|
||||
},
|
||||
launcher = UI.Chooser {
|
||||
x = 13, y = 2, width = 12,
|
||||
choices = {
|
||||
{ name = 'Overview', value = 'sys/apps/Overview.lua' },
|
||||
{ name = 'Shell', value = 'sys/apps/ShellLauncher.lua' },
|
||||
{ name = 'Custom', value = 'custom' },
|
||||
},
|
||||
},
|
||||
custom = UI.TextEntry {
|
||||
x = 13, ex = -3, y = 3,
|
||||
limit = 128,
|
||||
shadowText = 'File name',
|
||||
},
|
||||
button = UI.Button {
|
||||
x = 3, y = 5,
|
||||
text = 'Update',
|
||||
event = 'update',
|
||||
},
|
||||
labelText = UI.TextArea {
|
||||
x = 3, ex = -3, y = 7,
|
||||
textColor = colors.yellow,
|
||||
value = 'Choose an application launcher',
|
||||
},
|
||||
}
|
||||
|
||||
function tab:enable()
|
||||
local launcher = config.launcher and 'custom' or 'sys/apps/Overview.lua'
|
||||
local launcher = config.launcher and 'custom' or 'sys/apps/Overview.lua'
|
||||
|
||||
for _, v in pairs(self.launcher.choices) do
|
||||
if v.value == config.launcher then
|
||||
launcher = v.value
|
||||
break
|
||||
end
|
||||
end
|
||||
for _, v in pairs(self.launcher.choices) do
|
||||
if v.value == config.launcher then
|
||||
launcher = v.value
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
UI.Tab.enable(self)
|
||||
|
||||
self.launcher.value = launcher
|
||||
self.custom.enabled = launcher == 'custom'
|
||||
self.launcher.value = launcher
|
||||
self.custom.enabled = launcher == 'custom'
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'choice_change' then
|
||||
self.custom.enabled = event.value == 'custom'
|
||||
if self.custom.enabled then
|
||||
self.custom.value = config.launcher
|
||||
end
|
||||
self:draw()
|
||||
self.custom.enabled = event.value == 'custom'
|
||||
if self.custom.enabled then
|
||||
self.custom.value = config.launcher
|
||||
end
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'update' then
|
||||
local launcher
|
||||
elseif event.type == 'update' then
|
||||
local launcher
|
||||
|
||||
if self.launcher.value ~= 'custom' then
|
||||
launcher = self.launcher.value
|
||||
elseif fs.exists(self.custom.value) and not fs.isDir(self.custom.value) then
|
||||
launcher = self.custom.value
|
||||
end
|
||||
if self.launcher.value ~= 'custom' then
|
||||
launcher = self.launcher.value
|
||||
elseif fs.exists(self.custom.value) and not fs.isDir(self.custom.value) then
|
||||
launcher = self.custom.value
|
||||
end
|
||||
|
||||
if launcher then
|
||||
config.launcher = launcher
|
||||
Config.update('multishell', config)
|
||||
self:emit({ type = 'success_message', message = 'Updated' })
|
||||
else
|
||||
self:emit({ type = 'error_message', message = 'Invalid file' })
|
||||
end
|
||||
end
|
||||
if launcher then
|
||||
config.launcher = launcher
|
||||
Config.update('multishell', config)
|
||||
self:emit({ type = 'success_message', message = 'Updated' })
|
||||
else
|
||||
self:emit({ type = 'error_message', message = 'Invalid file' })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return tab
|
||||
|
@ -13,91 +13,91 @@ local tab = UI.Tab {
|
||||
accelerators = {
|
||||
enter = 'update_path',
|
||||
},
|
||||
help = 'add a new path',
|
||||
help = 'add a new path',
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 4, ey = -3,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'value' } },
|
||||
autospace = true,
|
||||
sortColumn = 'index',
|
||||
help = 'double-click to remove, shift-arrow to move',
|
||||
accelerators = {
|
||||
delete = 'remove',
|
||||
},
|
||||
sortColumn = 'index',
|
||||
help = 'double-click to remove, shift-arrow to move',
|
||||
accelerators = {
|
||||
delete = 'remove',
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar { },
|
||||
accelerators = {
|
||||
[ 'shift-up' ] = 'move_up',
|
||||
[ 'shift-down' ] = 'move_down',
|
||||
},
|
||||
statusBar = UI.StatusBar { },
|
||||
accelerators = {
|
||||
[ 'shift-up' ] = 'move_up',
|
||||
[ 'shift-down' ] = 'move_down',
|
||||
},
|
||||
}
|
||||
|
||||
function tab:updateList(path)
|
||||
self.grid.values = { }
|
||||
for k,v in ipairs(Util.split(path, '(.-):')) do
|
||||
table.insert(self.grid.values, { index = k, value = v })
|
||||
end
|
||||
self.grid:update()
|
||||
end
|
||||
self.grid:update()
|
||||
end
|
||||
|
||||
function tab:enable()
|
||||
local env = Config.load('shell')
|
||||
self:updateList(env.path)
|
||||
local env = Config.load('shell')
|
||||
self:updateList(env.path)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function tab:save()
|
||||
local t = { }
|
||||
for _, v in ipairs(self.grid.values) do
|
||||
table.insert(t, v.value)
|
||||
end
|
||||
local env = Config.load('shell')
|
||||
env.path = table.concat(t, ':')
|
||||
self:updateList(env.path)
|
||||
Config.update('shell', env)
|
||||
local t = { }
|
||||
for _, v in ipairs(self.grid.values) do
|
||||
table.insert(t, v.value)
|
||||
end
|
||||
local env = Config.load('shell')
|
||||
env.path = table.concat(t, ':')
|
||||
self:updateList(env.path)
|
||||
Config.update('shell', env)
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'update_path' then
|
||||
table.insert(self.grid.values, {
|
||||
value = self.entry.value,
|
||||
})
|
||||
if event.type == 'update_path' then
|
||||
table.insert(self.grid.values, {
|
||||
value = self.entry.value,
|
||||
})
|
||||
self:save()
|
||||
self.entry:reset()
|
||||
self.entry:draw()
|
||||
self.grid:draw()
|
||||
self.entry:draw()
|
||||
self.grid:draw()
|
||||
return true
|
||||
|
||||
elseif event.type == 'grid_select' or event.type == 'remove' then
|
||||
local selected = self.grid:getSelected()
|
||||
if selected then
|
||||
table.remove(self.grid.values, selected.index)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
elseif event.type == 'grid_select' or event.type == 'remove' then
|
||||
local selected = self.grid:getSelected()
|
||||
if selected then
|
||||
table.remove(self.grid.values, selected.index)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
elseif event.type == 'focus_change' then
|
||||
self.statusBar:setStatus(event.focused.help)
|
||||
|
||||
elseif event.type == 'move_up' then
|
||||
local entry = self.grid:getSelected()
|
||||
if entry.index > 1 then
|
||||
table.insert(self.grid.values, entry.index - 1, table.remove(self.grid.values, entry.index))
|
||||
self.grid:setIndex(entry.index - 1)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
elseif event.type == 'move_up' then
|
||||
local entry = self.grid:getSelected()
|
||||
if entry.index > 1 then
|
||||
table.insert(self.grid.values, entry.index - 1, table.remove(self.grid.values, entry.index))
|
||||
self.grid:setIndex(entry.index - 1)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'move_down' then
|
||||
local entry = self.grid:getSelected()
|
||||
if entry.index < #self.grid.values then
|
||||
table.insert(self.grid.values, entry.index + 1, table.remove(self.grid.values, entry.index))
|
||||
self.grid:setIndex(entry.index + 1)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
elseif event.type == 'move_down' then
|
||||
local entry = self.grid:getSelected()
|
||||
if entry.index < #self.grid.values then
|
||||
table.insert(self.grid.values, entry.index + 1, table.remove(self.grid.values, entry.index))
|
||||
self.grid:setIndex(entry.index + 1)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return tab
|
||||
|
@ -13,91 +13,91 @@ local tab = UI.Tab {
|
||||
accelerators = {
|
||||
enter = 'update_path',
|
||||
},
|
||||
help = 'add a new path (reboot required)',
|
||||
help = 'add a new path (reboot required)',
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 4, ey = -3,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'value' } },
|
||||
autospace = true,
|
||||
sortColumn = 'index',
|
||||
help = 'double-click to remove, shift-arrow to move',
|
||||
accelerators = {
|
||||
delete = 'remove',
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar { },
|
||||
accelerators = {
|
||||
[ 'shift-up' ] = 'move_up',
|
||||
[ 'shift-down' ] = 'move_down',
|
||||
},
|
||||
autospace = true,
|
||||
sortColumn = 'index',
|
||||
help = 'double-click to remove, shift-arrow to move',
|
||||
accelerators = {
|
||||
delete = 'remove',
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar { },
|
||||
accelerators = {
|
||||
[ 'shift-up' ] = 'move_up',
|
||||
[ 'shift-down' ] = 'move_down',
|
||||
},
|
||||
}
|
||||
|
||||
function tab:updateList(lua_path)
|
||||
self.grid.values = { }
|
||||
for k,v in ipairs(Util.split(lua_path, '(.-);')) do
|
||||
table.insert(self.grid.values, { index = k, value = v })
|
||||
end
|
||||
self.grid:update()
|
||||
end
|
||||
self.grid:update()
|
||||
end
|
||||
|
||||
function tab:enable()
|
||||
local env = Config.load('shell')
|
||||
self:updateList(env.lua_path)
|
||||
local env = Config.load('shell')
|
||||
self:updateList(env.lua_path)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function tab:save()
|
||||
local t = { }
|
||||
for _, v in ipairs(self.grid.values) do
|
||||
table.insert(t, v.value)
|
||||
end
|
||||
local env = Config.load('shell')
|
||||
env.lua_path = table.concat(t, ';')
|
||||
self:updateList(env.lua_path)
|
||||
Config.update('shell', env)
|
||||
local t = { }
|
||||
for _, v in ipairs(self.grid.values) do
|
||||
table.insert(t, v.value)
|
||||
end
|
||||
local env = Config.load('shell')
|
||||
env.lua_path = table.concat(t, ';')
|
||||
self:updateList(env.lua_path)
|
||||
Config.update('shell', env)
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'update_path' then
|
||||
table.insert(self.grid.values, {
|
||||
value = self.entry.value,
|
||||
})
|
||||
self:save()
|
||||
if event.type == 'update_path' then
|
||||
table.insert(self.grid.values, {
|
||||
value = self.entry.value,
|
||||
})
|
||||
self:save()
|
||||
self.entry:reset()
|
||||
self.entry:draw()
|
||||
self.grid:draw()
|
||||
self.entry:draw()
|
||||
self.grid:draw()
|
||||
return true
|
||||
|
||||
elseif event.type == 'grid_select' or event.type == 'remove' then
|
||||
local selected = self.grid:getSelected()
|
||||
if selected then
|
||||
table.remove(self.grid.values, selected.index)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
elseif event.type == 'grid_select' or event.type == 'remove' then
|
||||
local selected = self.grid:getSelected()
|
||||
if selected then
|
||||
table.remove(self.grid.values, selected.index)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
elseif event.type == 'focus_change' then
|
||||
self.statusBar:setStatus(event.focused.help)
|
||||
|
||||
elseif event.type == 'move_up' then
|
||||
local entry = self.grid:getSelected()
|
||||
if entry.index > 1 then
|
||||
table.insert(self.grid.values, entry.index - 1, table.remove(self.grid.values, entry.index))
|
||||
self.grid:setIndex(entry.index - 1)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
elseif event.type == 'move_up' then
|
||||
local entry = self.grid:getSelected()
|
||||
if entry.index > 1 then
|
||||
table.insert(self.grid.values, entry.index - 1, table.remove(self.grid.values, entry.index))
|
||||
self.grid:setIndex(entry.index - 1)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'move_down' then
|
||||
local entry = self.grid:getSelected()
|
||||
if entry.index < #self.grid.values then
|
||||
table.insert(self.grid.values, entry.index + 1, table.remove(self.grid.values, entry.index))
|
||||
self.grid:setIndex(entry.index + 1)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
elseif event.type == 'move_down' then
|
||||
local entry = self.grid:getSelected()
|
||||
if entry.index < #self.grid.values then
|
||||
table.insert(self.grid.values, entry.index + 1, table.remove(self.grid.values, entry.index))
|
||||
self.grid:setIndex(entry.index + 1)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return tab
|
||||
|
@ -9,134 +9,134 @@ local config = Config.load('shellprompt')
|
||||
|
||||
local allColors = { }
|
||||
for k,v in pairs(colors) do
|
||||
if type(v) == 'number' then
|
||||
table.insert(allColors, { name = k, value = v })
|
||||
end
|
||||
if type(v) == 'number' then
|
||||
table.insert(allColors, { name = k, value = v })
|
||||
end
|
||||
end
|
||||
|
||||
local defaults = {
|
||||
textColor = colors.white,
|
||||
commandTextColor = colors.yellow,
|
||||
directoryTextColor = colors.orange,
|
||||
directoryBackgroundColor = colors.black,
|
||||
promptTextColor = colors.blue,
|
||||
promptBackgroundColor = colors.black,
|
||||
directoryColor = colors.green,
|
||||
fileColor = colors.white,
|
||||
backgroundColor = colors.black,
|
||||
textColor = colors.white,
|
||||
commandTextColor = colors.yellow,
|
||||
directoryTextColor = colors.orange,
|
||||
directoryBackgroundColor = colors.black,
|
||||
promptTextColor = colors.blue,
|
||||
promptBackgroundColor = colors.black,
|
||||
directoryColor = colors.green,
|
||||
fileColor = colors.white,
|
||||
backgroundColor = colors.black,
|
||||
}
|
||||
local _colors = config.color or Util.shallowCopy(defaults)
|
||||
|
||||
local allSettings = { }
|
||||
for k, v in pairs(defaults) do
|
||||
table.insert(allSettings, { name = k })
|
||||
table.insert(allSettings, { name = k })
|
||||
end
|
||||
|
||||
-- temp
|
||||
if not _colors.backgroundColor then
|
||||
_colors.backgroundColor = colors.black
|
||||
_colors.fileColor = colors.white
|
||||
_colors.backgroundColor = colors.black
|
||||
_colors.fileColor = colors.white
|
||||
end
|
||||
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Shell',
|
||||
description = 'Shell options',
|
||||
grid1 = UI.ScrollingGrid {
|
||||
y = 2, ey = -10, x = 3, ex = -16,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'name' } },
|
||||
values = allSettings,
|
||||
sortColumn = 'name',
|
||||
},
|
||||
grid2 = UI.ScrollingGrid {
|
||||
y = 2, ey = -10, x = -14, ex = -3,
|
||||
description = 'Shell options',
|
||||
grid1 = UI.ScrollingGrid {
|
||||
y = 2, ey = -10, x = 3, ex = -16,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'name' } },
|
||||
values = allColors,
|
||||
sortColumn = 'name',
|
||||
values = allSettings,
|
||||
sortColumn = 'name',
|
||||
},
|
||||
grid2 = UI.ScrollingGrid {
|
||||
y = 2, ey = -10, x = -14, ex = -3,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'name' } },
|
||||
values = allColors,
|
||||
sortColumn = 'name',
|
||||
},
|
||||
directoryLabel = UI.Text {
|
||||
x = 2, y = -2,
|
||||
value = 'Display directory',
|
||||
},
|
||||
directory = UI.Checkbox {
|
||||
x = 20, y = -2,
|
||||
value = config.displayDirectory
|
||||
},
|
||||
reset = UI.Button {
|
||||
x = -18, y = -2,
|
||||
text = 'Reset',
|
||||
event = 'reset',
|
||||
},
|
||||
button = UI.Button {
|
||||
x = -9, y = -2,
|
||||
text = 'Update',
|
||||
event = 'update',
|
||||
},
|
||||
display = UI.Window {
|
||||
x = 3, ex = -3, y = -8, height = 5,
|
||||
},
|
||||
directoryLabel = UI.Text {
|
||||
x = 2, y = -2,
|
||||
value = 'Display directory',
|
||||
},
|
||||
directory = UI.Checkbox {
|
||||
x = 20, y = -2,
|
||||
value = config.displayDirectory
|
||||
},
|
||||
reset = UI.Button {
|
||||
x = -18, y = -2,
|
||||
text = 'Reset',
|
||||
event = 'reset',
|
||||
},
|
||||
button = UI.Button {
|
||||
x = -9, y = -2,
|
||||
text = 'Update',
|
||||
event = 'update',
|
||||
},
|
||||
display = UI.Window {
|
||||
x = 3, ex = -3, y = -8, height = 5,
|
||||
},
|
||||
}
|
||||
|
||||
function tab.grid2:getRowTextColor(row)
|
||||
local selected = tab.grid1:getSelected()
|
||||
if _colors[selected.name] == row.value then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row)
|
||||
local selected = tab.grid1:getSelected()
|
||||
if _colors[selected.name] == row.value then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row)
|
||||
end
|
||||
|
||||
function tab.display:draw()
|
||||
self:clear(_colors.backgroundColor)
|
||||
local offset = 0
|
||||
if config.displayDirectory then
|
||||
self:write(1, 1,
|
||||
'==' .. os.getComputerLabel() .. ':/dir/etc',
|
||||
_colors.directoryBackgroundColor, _colors.directoryTextColor)
|
||||
offset = 1
|
||||
end
|
||||
self:clear(_colors.backgroundColor)
|
||||
local offset = 0
|
||||
if config.displayDirectory then
|
||||
self:write(1, 1,
|
||||
'==' .. os.getComputerLabel() .. ':/dir/etc',
|
||||
_colors.directoryBackgroundColor, _colors.directoryTextColor)
|
||||
offset = 1
|
||||
end
|
||||
|
||||
self:write(1, 1 + offset, '$ ',
|
||||
_colors.promptBackgroundColor, _colors.promptTextColor)
|
||||
self:write(1, 1 + offset, '$ ',
|
||||
_colors.promptBackgroundColor, _colors.promptTextColor)
|
||||
|
||||
self:write(3, 1 + offset, 'ls /',
|
||||
_colors.backgroundColor, _colors.commandTextColor)
|
||||
self:write(3, 1 + offset, 'ls /',
|
||||
_colors.backgroundColor, _colors.commandTextColor)
|
||||
|
||||
self:write(1, 2 + offset, 'sys usr',
|
||||
_colors.backgroundColor, _colors.directoryColor)
|
||||
self:write(1, 2 + offset, 'sys usr',
|
||||
_colors.backgroundColor, _colors.directoryColor)
|
||||
|
||||
self:write(1, 3 + offset, 'startup',
|
||||
_colors.backgroundColor, _colors.fileColor)
|
||||
self:write(1, 3 + offset, 'startup',
|
||||
_colors.backgroundColor, _colors.fileColor)
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type =='checkbox_change' then
|
||||
config.displayDirectory = not not event.checked
|
||||
self.display:draw()
|
||||
if event.type =='checkbox_change' then
|
||||
config.displayDirectory = not not event.checked
|
||||
self.display:draw()
|
||||
|
||||
elseif event.type == 'grid_focus_row' and event.element == self.grid1 then
|
||||
self.grid2:draw()
|
||||
elseif event.type == 'grid_focus_row' and event.element == self.grid1 then
|
||||
self.grid2:draw()
|
||||
|
||||
elseif event.type == 'grid_select' and event.element == self.grid2 then
|
||||
_colors[tab.grid1:getSelected().name] = event.selected.value
|
||||
self.display:draw()
|
||||
self.grid2:draw()
|
||||
elseif event.type == 'grid_select' and event.element == self.grid2 then
|
||||
_colors[tab.grid1:getSelected().name] = event.selected.value
|
||||
self.display:draw()
|
||||
self.grid2:draw()
|
||||
|
||||
elseif event.type == 'reset' then
|
||||
config.color = defaults
|
||||
config.displayDirectory = true
|
||||
self.directory.value = true
|
||||
_colors = Util.shallowCopy(defaults)
|
||||
elseif event.type == 'reset' then
|
||||
config.color = defaults
|
||||
config.displayDirectory = true
|
||||
self.directory.value = true
|
||||
_colors = Util.shallowCopy(defaults)
|
||||
|
||||
Config.update('shellprompt', config)
|
||||
self:draw()
|
||||
Config.update('shellprompt', config)
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'update' then
|
||||
config.color = _colors
|
||||
Config.update('shellprompt', config)
|
||||
elseif event.type == 'update' then
|
||||
config.color = _colors
|
||||
Config.update('shellprompt', config)
|
||||
|
||||
end
|
||||
return UI.Tab.eventHandler(self, event)
|
||||
end
|
||||
return UI.Tab.eventHandler(self, event)
|
||||
end
|
||||
|
||||
return tab
|
||||
|
@ -1,22 +1,22 @@
|
||||
local function completeMultipleChoice(sText, tOptions, bAddSpaces)
|
||||
local tResults = { }
|
||||
for n = 1,#tOptions do
|
||||
local sOption = tOptions[n]
|
||||
if #sOption + (bAddSpaces and 1 or 0) > #sText and string.sub(sOption, 1, #sText) == sText then
|
||||
local sResult = string.sub(sOption, #sText + 1)
|
||||
if bAddSpaces then
|
||||
table.insert(tResults, sResult .. " ")
|
||||
else
|
||||
table.insert(tResults, sResult)
|
||||
end
|
||||
end
|
||||
end
|
||||
return tResults
|
||||
local tResults = { }
|
||||
for n = 1,#tOptions do
|
||||
local sOption = tOptions[n]
|
||||
if #sOption + (bAddSpaces and 1 or 0) > #sText and string.sub(sOption, 1, #sText) == sText then
|
||||
local sResult = string.sub(sOption, #sText + 1)
|
||||
if bAddSpaces then
|
||||
table.insert(tResults, sResult .. " ")
|
||||
else
|
||||
table.insert(tResults, sResult)
|
||||
end
|
||||
end
|
||||
end
|
||||
return tResults
|
||||
end
|
||||
|
||||
_ENV.shell.setCompletionFunction("sys/apps/package.lua",
|
||||
function(_, index, text)
|
||||
if index == 1 then
|
||||
return completeMultipleChoice(text, { "install ", "update ", "uninstall " })
|
||||
end
|
||||
end)
|
||||
function(_, index, text)
|
||||
if index == 1 then
|
||||
return completeMultipleChoice(text, { "install ", "update ", "uninstall " })
|
||||
end
|
||||
end)
|
||||
|
@ -4,8 +4,8 @@ local shell = _ENV.shell
|
||||
|
||||
local config = Config.load('os')
|
||||
if not config.welcomed and shell.openForegroundTab then
|
||||
config.welcomed = true
|
||||
Config.update('os', config)
|
||||
config.welcomed = true
|
||||
Config.update('os', config)
|
||||
|
||||
shell.openForegroundTab('Welcome')
|
||||
shell.openForegroundTab('Welcome')
|
||||
end
|
||||
|
@ -6,47 +6,47 @@ local os = _G.os
|
||||
local peripheral = _G.peripheral
|
||||
|
||||
local containers = {
|
||||
manipulator = true,
|
||||
neuralInterface = true,
|
||||
manipulator = true,
|
||||
neuralInterface = true,
|
||||
}
|
||||
|
||||
local function getModules(dev, side)
|
||||
local list = { }
|
||||
local list = { }
|
||||
|
||||
if dev then
|
||||
for _, module in pairs(dev.listModules()) do
|
||||
list[module] = Util.shallowCopy(dev)
|
||||
list[module].name = module
|
||||
list[module].type = module
|
||||
list[module].side = side
|
||||
end
|
||||
end
|
||||
return list
|
||||
if dev then
|
||||
for _, module in pairs(dev.listModules()) do
|
||||
list[module] = Util.shallowCopy(dev)
|
||||
list[module].name = module
|
||||
list[module].type = module
|
||||
list[module].side = side
|
||||
end
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
for _,v in pairs(device) do
|
||||
if containers[v.type] then
|
||||
local list = getModules(v, v.side)
|
||||
for k, dev in pairs(list) do
|
||||
-- neural and attached modules have precedence over manipulator modules
|
||||
if not device[k] or v.type ~= 'manipulator' then
|
||||
device[k] = dev
|
||||
end
|
||||
end
|
||||
end
|
||||
if containers[v.type] then
|
||||
local list = getModules(v, v.side)
|
||||
for k, dev in pairs(list) do
|
||||
-- neural and attached modules have precedence over manipulator modules
|
||||
if not device[k] or v.type ~= 'manipulator' then
|
||||
device[k] = dev
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- register modules as peripherals
|
||||
kernel.hook('device_attach', function(_, eventData)
|
||||
local dev = eventData[2]
|
||||
local dev = eventData[2]
|
||||
|
||||
if dev and containers[dev.type] then
|
||||
local list = getModules(peripheral.wrap(dev.side), dev.side)
|
||||
for k,v in pairs(list) do
|
||||
if not device[k] or dev.type ~= 'manipulator' then
|
||||
device[k] = v
|
||||
os.queueEvent('device_attach', k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
if dev and containers[dev.type] then
|
||||
local list = getModules(peripheral.wrap(dev.side), dev.side)
|
||||
for k,v in pairs(list) do
|
||||
if not device[k] or dev.type ~= 'manipulator' then
|
||||
device[k] = v
|
||||
os.queueEvent('device_attach', k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
@ -2,26 +2,26 @@ local device = _G.device
|
||||
local kernel = _G.kernel
|
||||
|
||||
local function register(v)
|
||||
if v and v.isWireless and v.isAccessPoint and v.getNamesRemote then
|
||||
v._children = { }
|
||||
for _, name in pairs(v.getNamesRemote()) do
|
||||
local dev = v.getMethodsRemote(name)
|
||||
if dev then
|
||||
dev.name = name
|
||||
dev.side = name
|
||||
dev.type = v.getTypeRemote(name)
|
||||
device[name] = dev
|
||||
table.insert(v._children, dev)
|
||||
end
|
||||
end
|
||||
end
|
||||
if v and v.isWireless and v.isAccessPoint and v.getNamesRemote then
|
||||
v._children = { }
|
||||
for _, name in pairs(v.getNamesRemote()) do
|
||||
local dev = v.getMethodsRemote(name)
|
||||
if dev then
|
||||
dev.name = name
|
||||
dev.side = name
|
||||
dev.type = v.getTypeRemote(name)
|
||||
device[name] = dev
|
||||
table.insert(v._children, dev)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _,v in pairs(device) do
|
||||
register(v)
|
||||
register(v)
|
||||
end
|
||||
|
||||
-- register oc devices as peripherals
|
||||
kernel.hook('device_attach', function(_, eventData)
|
||||
register(device[eventData[2]])
|
||||
register(device[eventData[2]])
|
||||
end)
|
||||
|
@ -6,7 +6,9 @@ local help = _G.help
|
||||
local shell = _ENV.shell
|
||||
|
||||
if not fs.exists('usr/config/packages') then
|
||||
Packages:downloadList()
|
||||
pcall(function()
|
||||
Packages:downloadList()
|
||||
end)
|
||||
end
|
||||
|
||||
local appPaths = Util.split(shell.path(), '(.-):')
|
||||
|
@ -52,6 +52,7 @@ function turtle.resetState()
|
||||
state.movePolicy = _defaultMove
|
||||
state.moveCallback = noop
|
||||
state.blacklist = nil
|
||||
state.reference = nil -- gps reference when converting to relative coords
|
||||
Pathing.reset()
|
||||
return true
|
||||
end
|
||||
@ -77,8 +78,6 @@ local function _dig(name, inspect, dig)
|
||||
return dig()
|
||||
end
|
||||
|
||||
-- override dig
|
||||
-- optionally check that the block is a certain type
|
||||
function turtle.dig(s)
|
||||
return _dig(s, turtle.inspect, turtle.native.dig)
|
||||
end
|
||||
@ -359,7 +358,7 @@ function turtle.set(args)
|
||||
turtle.setDigPolicy(turtle.getPolicy(v))
|
||||
|
||||
elseif k == 'movePolicy' then
|
||||
turtle.setMovePolicy(turtle.getPolicy(v))
|
||||
state.movePolicy = turtle.getPolicy(v)
|
||||
|
||||
elseif k == 'movementStrategy' then
|
||||
turtle.setMovementStrategy(v)
|
||||
@ -379,6 +378,9 @@ function turtle.set(args)
|
||||
elseif k == 'blacklist' then
|
||||
state.blacklist = v
|
||||
|
||||
elseif k == 'reference' then
|
||||
state.reference = v
|
||||
|
||||
else
|
||||
error('Invalid turle.set: ' .. tostring(k))
|
||||
end
|
||||
@ -426,6 +428,10 @@ function turtle.setHeading(heading)
|
||||
return false, 'Invalid heading'
|
||||
end
|
||||
|
||||
if heading == turtle.point.heading then
|
||||
return turtle.point
|
||||
end
|
||||
|
||||
local fi = Point.facings[heading]
|
||||
if not fi then
|
||||
return false, 'Invalid heading'
|
||||
|
Loading…
Reference in New Issue
Block a user