opus/apps/Script.lua

562 lines
12 KiB
Lua

require = requireInjector(getfenv(1))
local Event = require('event')
local UI = require('ui')
local Socket = require('socket')
local Config = require('config')
local GROUPS_PATH = '/apps/groups'
local SCRIPTS_PATH = '/apps/scripts'
multishell.setTitle(multishell.getCurrent(), 'Script')
UI:configure('Script', ...)
local config = {
showGroups = false,
variables = [[{
COMPUTER_ID = os.getComputerID(),
}]],
}
Config.load('Script', config)
local width = math.floor(UI.term.width / 2) - 1
if UI.term.width % 2 ~= 0 then
width = width + 1
end
function processVariables(script)
local fn = loadstring('return ' .. config.variables)
if fn then
local variables = fn()
for k,v in pairs(variables) do
local token = string.format('{%s}', k)
script = script:gsub(token, v)
end
end
return script
end
function invokeScript(computer, scriptName)
local script = Util.readFile(scriptName)
if not script then
print('Unable to read script file')
end
local socket = Socket.connect(computer.id, 161)
if not socket then
print('Unable to connect to ' .. computer.id)
return
end
script = processVariables(script)
Util.print('Running %s on %s', scriptName, computer.label)
socket:write({ type = 'script', args = script })
--[[
local response = socket:read(2)
if response and response.result then
if type(response.result) == 'table' then
print(textutils.serialize(response.result))
else
print(tostring(response.result))
end
else
printError('No response')
end
--]]
socket:close()
end
function runScript(computerOrGroup, scriptName)
if computerOrGroup.id then
invokeScript(computerOrGroup, scriptName)
else
local list = computerOrGroup.list
if computerOrGroup.path then
list = Util.readTable(computerOrGroup.path)
end
if list then
for _,computer in pairs(list) do
invokeScript(computer, scriptName)
end
end
end
end
local function getActiveComputers(t)
t = t or { }
Util.clear(t)
for k,computer in pairs(_G.network) do
if computer.active then
t[k] = computer
end
end
return t
end
local function getTurtleList()
local turtles = {
label = 'Turtles',
list = { },
}
for k,computer in pairs(getActiveComputers()) do
if computer.fuel then
turtles.list[k] = computer
end
end
return turtles
end
local args = { ... }
if #args == 2 then
local key = args[1]
local script = args[2]
local target
if tonumber(key) then
target = _G.network[tonumber(key)]
elseif key == 'All' then
target = {
list = Util.shallowCopy(getActiveComputers()),
}
elseif key == 'Localhost' then
target = { id = os.getComputerID() }
elseif key == 'Turtles' then
target = getTurtleList()
else
target = Util.readTable(fs.combine(GROUPS_PATH, key))
end
if not target then
error('Syntax: Script <ID or group> <script>')
end
runScript(target, fs.combine(SCRIPTS_PATH, script))
return
end
local function getListing(t, path)
Util.clear(t)
local files = fs.list(path)
for _,f in pairs(files) do
table.insert(t, { label = f, path = fs.combine(path, f) })
end
end
local mainPage = UI.Page({
menuBar = UI.MenuBar({
buttons = {
{ text = 'Groups', event = 'groups' },
{ text = 'Scripts', event = 'scripts' },
{ text = 'Toggle', event = 'toggle' },
},
}),
computers = UI.ScrollingGrid({
y = 2,
height = UI.term.height-3,
columns = {
{ heading = 'Label', key = 'label', width = width },
},
width = width,
sortColumn = 'label',
}),
scripts = UI.ScrollingGrid({
columns = {
{ heading = 'Name', key = 'label', width = width },
},
sortColumn = 'label',
height = UI.term.height - 3,
width = width,
x = UI.term.width - width + 1,
y = 2,
}),
statusBar = UI.StatusBar({
columns = {
{ '', 'status', 4 },
{ '', 'fuelF', 5 },
{ '', 'distanceF', 4 },
},
autospace = true,
}),
accelerators = {
q = 'quit',
},
})
local editorPage = UI.Page({
menuBar = UI.MenuBar({
showBackButton = true,
buttons = {
{ text = 'Save', event = 'save', help = 'Save this group' },
},
}),
grid1 = UI.ScrollingGrid({
columns = {
{ heading = 'Name', key = 'label', width = width },
},
sortColumn = 'label',
height = UI.term.height - 4,
width = width,
y = 3,
}),
right = UI.Button({
text = '>',
event = 'right',
x = width - 2,
y = 2,
width = 3,
}),
left = UI.Button({
text = '<',
event = 'left',
x = UI.term.width - width + 1,
y = 2,
width = 3,
}),
grid2 = UI.ScrollingGrid({
columns = {
{ heading = 'Name', key = 'label', width = width },
},
sortColumn = 'label',
height = UI.term.height - 4,
width = width,
x = UI.term.width - width + 1,
y = 3,
}),
statusBar = UI.StatusBar(),
accelerators = {
q = 'back',
},
})
local groupsPage = UI.Page({
menuBar = UI.MenuBar({
showBackButton = true,
buttons = {
{ text = 'Add', event = 'add' },
{ text = 'Edit', event = 'edit' },
{ text = 'Delete', event = 'delete' },
},
}),
grid = UI.ScrollingGrid({
y = 2,
height = UI.term.height-2,
columns = {
{ heading = 'Name', key = 'label' },
},
sortColumn = 'label',
autospace = true,
}),
statusBar = UI.StatusBar(),
accelerators = {
q = 'back',
},
})
local scriptsPage = UI.Page({
menuBar = UI.MenuBar({
showBackButton = true,
buttons = {
{ text = 'Add', event = 'add' },
{ text = 'Edit', event = 'edit' },
{ text = 'Delete', event = 'delete' },
},
}),
grid = UI.ScrollingGrid({
y = 2,
height = UI.term.height-2,
columns = {
{ heading = 'Name', key = 'label' },
},
sortColumn = 'label',
autospace = true,
}),
statusBar = UI.StatusBar(),
accelerators = {
a = 'add',
e = 'edit',
delete = 'delete',
q = 'back',
},
})
function editorPage:enable()
self:focusFirst()
local groupPath = fs.combine(GROUPS_PATH, self.groupName)
if fs.exists(groupPath) then
self.grid1.values = Util.readTable(groupPath)
else
Util.clear(self.grid1.values)
end
self.grid1:update()
UI.Page.enable(self)
end
function editorPage.grid2:draw()
getActiveComputers(self.values)
for k in pairs(editorPage.grid1.values) do
self.values[k] = nil
end
self:update()
UI.ScrollingGrid.draw(self)
end
function editorPage:eventHandler(event)
if event.type == 'back' then
UI:setPage(groupsPage)
elseif event.type == 'left' then
local computer = self.grid2:getSelected()
self.grid1.values[computer.id] = computer
self.grid1:update()
self.grid1:draw()
self.grid2:draw()
elseif event.type == 'right' then
local computer = self.grid1:getSelected()
self.grid1.values[computer.id] = nil
self.grid1:update()
self.grid1:draw()
self.grid2:draw()
elseif event.type == 'save' then
Util.writeTable(fs.combine(GROUPS_PATH, self.groupName), self.grid1.values)
UI:setPage(groupsPage)
end
return UI.Page.eventHandler(self, event)
end
local function nameDialog(f)
local dialog = UI.Dialog({
x = (UI.term.width - 28) / 2,
width = 28,
textEntry = UI.TextEntry({ x = 4, y = 3, width = 20, limit = 20 })
})
dialog.titleBar.title = 'Enter Name'
dialog.eventHandler = function(self, event)
if event.type == 'accept' then
local name = self.textEntry.value
if name then
f(name)
else
self.statusBar:timedStatus('Invalid Name', 3)
end
return true
end
return UI.Dialog.eventHandler(self, event)
end
dialog:setFocus(dialog.textEntry)
UI:setPage(dialog)
end
function groupsPage:draw()
getListing(self.grid.values, GROUPS_PATH)
self.grid:update()
UI.Page.draw(self)
end
function groupsPage:enable()
self:focusFirst()
UI.Page.enable(self)
end
function groupsPage:eventHandler(event)
if event.type == 'back' then
UI:setPage(mainPage)
elseif event.type == 'add' then
nameDialog(function(name)
editorPage.groupName = name
UI:setPage(editorPage)
end)
elseif event.type == 'delete' then
fs.delete(fs.combine(GROUPS_PATH, self.grid:getSelected().label))
self:draw()
elseif event.type == 'edit' then
editorPage.groupName = self.grid:getSelected().label
UI:setPage(editorPage)
end
return UI.Page.eventHandler(self, event)
end
function scriptsPage:draw()
getListing(self.grid.values, SCRIPTS_PATH)
self.grid:update()
UI.Page.draw(self)
end
function scriptsPage:enable()
self:focusFirst()
UI.Page.enable(self)
end
function scriptsPage:eventHandler(event)
if event.type == 'back' then
UI:setPreviousPage()
elseif event.type == 'add' then
nameDialog(function(name)
shell.run('edit ' .. fs.combine(SCRIPTS_PATH, name))
UI:setPreviousPage()
end)
elseif event.type == 'edit' then
local name = fs.combine(SCRIPTS_PATH, self.grid:getSelected().label)
shell.run('edit ' .. name)
self:draw()
elseif event.type == 'delete' then
local name = fs.combine(SCRIPTS_PATH, self.grid:getSelected().label)
fs.delete(name)
self:draw()
end
return UI.Page.eventHandler(self, event)
end
function mainPage:eventHandler(event)
if event.type == 'quit' then
Event.exitPullEvents()
elseif event.type == 'groups' then
UI:setPage(groupsPage)
elseif event.type == 'scripts' then
UI:setPage(scriptsPage)
elseif event.type == 'toggle' then
config.showGroups = not config.showGroups
local text = 'Computers'
if config.showGroups then
text = 'Groups'
end
-- self.statusBar.toggleButton.text = text
self:draw()
Config.update('Script', config)
elseif event.type == 'grid_focus_row' then
local computer = self.computers:getSelected()
self.statusBar.values = { computer }
self.statusBar:draw()
elseif event.type == 'grid_select' then
local script = self.scripts:getSelected()
local computer = self.computers:getSelected()
self:clear()
self:sync()
self.enabled = false
runScript(computer, script.path)
print()
print('Press any key to continue...')
while true do
local e = os.pullEvent()
if e == 'char' or e == 'key' or e == 'mouse_click' then
break
end
end
self.enabled = true
self:draw()
end
return UI.Page.eventHandler(self, event)
end
function mainPage.statusBar:draw()
local computer = self.values[1]
if computer then
if computer.fuel then
computer.fuelF = string.format("%dk", math.floor(computer.fuel/1000))
end
if computer.distance then
computer.distanceF = Util.round(computer.distance, 1)
end
mainPage.statusBar:adjustWidth()
end
UI.StatusBar.draw(self)
end
function mainPage:draw()
getListing(self.scripts.values, SCRIPTS_PATH)
if config.showGroups then
getListing(self.computers.values, GROUPS_PATH)
table.insert(self.computers.values, {
label = 'All',
list = getActiveComputers(),
})
table.insert(self.computers.values, getTurtleList())
table.insert(self.computers.values, {
label = 'Localhost',
id = os.getComputerID(),
})
else
getActiveComputers(self.computers.values)
end
self.scripts:update()
self.computers:update()
UI.Page.draw(self)
end
if not fs.exists(SCRIPTS_PATH) then
fs.makeDir(SCRIPTS_PATH)
end
if not fs.exists(GROUPS_PATH) then
fs.makeDir(GROUPS_PATH)
end
Event.addHandler('network_attach', function()
if mainPage.enabled then
mainPage:draw()
end
end)
Event.addHandler('network_detach', function()
if mainPage.enabled then
mainPage:draw()
end
end)
function statusUpdate()
while true do
if mainPage.enabled then
local selected = mainPage.computers:getSelected()
if selected then
local computer = _G.network[selected.id]
mainPage.statusBar.values = { computer }
mainPage.statusBar:draw()
mainPage:sync()
end
end
os.sleep(1)
end
end
UI:setPage(mainPage)
Event.pullEvents(statusUpdate)
UI.term:reset()