· 7 years ago · Dec 08, 2018, 09:42 PM
1-- ################################################
2-- # The Guard 2.2 #
3-- # #
4-- # 03.2016 by: Dominik Rzepka #
5-- ################################################
6
7--[[
8 ## Description ##
9 This program serves as a security system control center. Most of the
10 functionality is acheived by using "OpenSecurity" Minecraft mod.
11
12 ## Technical description ##
13 Previous version of this server (1.0) used separate computers to communicate
14 with security components - they had the 'micro' program installed. In newer
15 versions this architecture was abandoned. It was superseded by a modular
16 architecture and all components have to be connected directly to the server.
17 This solution has one major drawback: maximum number of simultaneously
18 connected components is 64.
19
20 Now this server acts merely as a container - it provides only very little
21 functionality on its own. Instead, external modules can be attached in order
22 to acheive desired functionality. The module catalogue offers an easy
23 way to manage all available modules.
24
25 Components connected to server aren't automatically available to modules
26 (that is only if they try to obtain components through the API). Instead,
27 they have to be manually registered first.
28
29 # Idea of actions ##
30 Modules can provide or use so-called actions: they allow modules to perform
31 predefined tasks defined in one of active modules. Each action can handle
32 up to 2 optional parameters.
33
34 Thanks to the concept actions, modules don't use each other's functionality
35 directly. Instead, they communicate through the_guard server and on it lies
36 the responsibility to find a module with specified action and call it. It
37 allows to create complex modules whose functionality can be flexibly
38 configured by user - by specifying what actions to call and when.
39
40 ## List of officially supported modules ##
41 - levels (mod_tg_levels) - security levels management
42 - logs (mod_tg_logs) - action-driven logger
43 - io (mod_tg_io) - IO devices support (redstone/project red/network)
44 - auth (mot_tg_auth) - user authentication (keypads, biometric scanners, cards)
45 - turrets (mod_tg_turrets) - energy turrets and motion sensors
46
47 ## Module architecture ##
48 In order for a module to be accepted by the server, it has to return
49 an associative table containing the following entries:
50 - name:string - name of a module
51 - version:string - version of a module
52 - id: number - unique module identifier
53 - apiLevel:number - supported server API version
54 - shape:string - module window shape ("normal", "landscape")
55 - actions:table - table with available actions
56 - setUI(window) - creates and returns main GUI, called after start()
57 - start(server:table):function - called on server start - used to initialize module
58 - stop(server_table):function - called on server stop
59 - pullEvent(...):function - event handler (only registered events)
60
61 Actions are a tasks that a module can perform. They are shared across
62 server instance - can be viewed as well as executed by any other module.
63 Actions' table structure is as follows:
64 [id:number - action identifier, unique within a module]
65 {
66 name:string - action name
67 type:string - action type
68 desc:string - action description
69 p1type:string - type of the 1st parameter
70 p2type:string - type o the 2nd parameter
71 p1desc:sting - description of the 1st parameter
72 p2desc:string - description of the 2nd parameter
73 exec:function - function containing task to execute
74 hidden:boolean - whether this action should be hidden
75 }
76
77 Action parameters are optional. For example, if only one parameter is used,
78 action should define only p1* fields.
79
80 Actions types are used to facilitate usage of the server (i.e. a module
81 may need only action of a specific type). Types used by server and
82 official modules:
83 * CORE - actions created by server
84 * LOG - log-related actions
85 * IO - actions related to redstone circuits
86 * LEVEL - actions that deal with security levels
87 * AUTH - user identity, authentication and authorization
88 * TURRET - actions defined in the mod_tg_turret module. Related to turrets and sensors.
89
90 Aside from actions, a module can listen for events. Server automatically emits the following events:
91 * {"components_changed", "<component_type> or nil"} - when a registered component was added, modified or removed
92
93 In order to receive every other type of event, a component must explicitly register
94 it through the server API.
95
96 ## Configuration scheme ##
97 settings: { - global program settings (/etc/the_guard/config.conf)
98 port: number - port used for network connection in modules (they may ingore this setting),
99 backupPort: number - data server port, used in backup/restore operations,
100 debugMode:bool - debug mode state,
101 dark:bool - whether to use dark mode,
102 saveOnExit:bool - whether configuration has to be saved again before exit
103 }
104
105 modules: { - module information (/etc/the_guard/modules.conf)
106 [zone:number] { - occupied zone
107 name:string - module name*
108 file:string - module file
109 version:string - module version*
110 shape:string - module dimensions*
111 }
112 ...
113 }
114
115 components: { - list of installed components (/etc/the_guard/components.conf)
116 {
117 id:number - component id
118 address:string - address
119 type: string - component type
120 name:string - name
121 state:bool - components status (enabled/disabled)
122 x: number - X coordinate of a module (optional)
123 y: number - Y coordinate of a module (optional)
124 z: number - Z coordinate of a module (optional)
125 }
126 ...
127 }
128
129 passwd:string - server master password (hashed using SHA-256)
130 (/etc/the_guard/passwd.bin)
131
132 Modules keep their configuration files in '/etc/the_guard/modules' directory.
133 Each module by default has one configuration file: <module_name>.conf
134
135 *Fields marked by asterisk aren't saved to configuration files
136
137 ## Interface ##
138 The server provices an interface (API) allowing modules to operate.
139 All of the interface's methods are documented in the code below
140 (prefixed with 'interface.').
141
142 Server window is divided to 5 zones. First 4 zones have identical
143 dimensions. 5. zone is wide and usually serves as a space for
144 a log module. Coordinates for module's GUI are relative to the
145 beginning of a zone. Module whose GUI goes beyond zone borders
146 will be disabled.
147]]
148
149local version = "2.2.2"
150local apiLevel = 3
151local args = {...}
152
153if args[1] == "version_check" then return version end
154local strict = args[1] == "strict"
155
156local computer = require("computer")
157local component = require("component")
158local event = require("event")
159local serial = require("serialization")
160local uni = require("unicode")
161local fs = require("filesystem")
162local term = require("term")
163local gml = require("gml")
164local dsapi = require("dsapi")
165local colors = require("colors")
166if not component.isAvailable("modem") then
167 io.stderr:write("Server requires a network card in order to work.")
168 return
169end
170local modem = component.modem
171
172local data = nil
173if component.isAvailable("data") then
174 data = component.data
175end
176if not data then
177 io.stderr:write("Server requires a data card tier 2 in order to work.")
178 return
179elseif not data.encrypt then
180 io.stderr:write("Used data card must be at least of tier 2.")
181 return
182end
183
184local resolution = {component.gpu.getResolution()}
185if not resolution[1] == 160 or not resolution[2] == 50 then
186 io.stderr:write("Server requires 160x50 screen resolution (current resolution: " .. tostring(resolution[1]) .. "x" .. tostring(resolution[2]))
187 return
188end
189
190-- # Configuration
191local passwd = nil -- master password
192local settings = {} -- settings
193local components = {} -- installed components
194local modules = {} -- available modules
195local bmodules = {} -- corrupted modules
196local pmodules = {} -- modules avaiting installation
197local token = nil -- device token (id)
198
199local configDir = "/etc/the_guard"
200local modulesDir = "/usr/bin/mod_tg"
201
202-- # Function declarations
203local silentLog = nil
204local GMLmessageBox = nil
205local GMLcontains = nil
206local GMLgetAppliedStyles = nil
207local GMLextractProperty = nil
208local GMLextractProperties = nil
209local GMLfindStyleProperties = nil
210local GMLcalcBody = nil
211
212-- # Zones
213local zones = {
214 [1] = {1, 1},
215 [2] = {70, 1},
216 [3] = {1, 21},
217 [4] = {70, 21},
218 [5] = {1, 41},
219 normal = {68, 19},
220 landscape = {158, 10}
221}
222
223-- # variables
224local gui = nil
225local mod = {}
226local intlog = ""
227local lastlog = {}
228local loglines = 0
229local internalMod = math.random()
230
231-- # Module interface
232local interface = {}
233local actions = {}
234local events = {}
235local eventsready = false
236local revents = {}
237local backgroundListener = nil
238
239--[[
240Loads module's primary configuration file
241 @mod - calling module
242 RET: table with config
243]]
244interface.loadConfig = function(mod)
245 local path = fs.concat("/etc/the_guard/modules", mod.name .. ".conf")
246 if fs.isDirectory(path) then
247 fs.remove(path)
248 elseif fs.exists(path) then
249 local f = io.open(path, "r")
250 if f then
251 local s, r = pcall(serial.unserialize, f:read("*a"))
252 if s then
253 f:close()
254 return r
255 else
256 f:close()
257 return {}
258 end
259 else
260 return {}
261 end
262 else
263 return {}
264 end
265end
266
267--[[
268Saves module's primary configuration to file
269 @mod - calling module
270 @tab - table with config
271]]
272interface.saveConfig = function(mod, tab)
273 if not fs.isDirectory("/etc/the_guard/modules") then
274 fs.makeDirectory("/etc/the_guard/modules")
275 end
276 if type(tab) == "table" then
277 local path = fs.concat("/etc/the_guard/modules", mod.name .. ".conf")
278 if fs.isDirectory(path) then
279 fs.remove(path)
280 else
281 local t = serial.serialize(tab)
282 if t then
283 local f = io.open(path, "w")
284 if f then
285 f:write(t)
286 f:close()
287 end
288 end
289 end
290 end
291end
292
293--[[
294Returns module by name
295 @mod - calling module
296 @name - target module's name
297 RET: desired module or nil
298]]
299interface.getModule = function(mod, name)
300 for _, t in pairs(modules) do
301 if t.name == name then return t end
302 end
303 return nil
304end
305
306--[[
307Registers new event
308 @mod - calling module
309 @name - event name
310]]
311interface.registerEvent = function(mod, name)
312 if not events[mod.name] then events[mod.name] = {} end
313 for _, n in pairs(events[mod.name]) do
314 if n == name then return end
315 end
316 table.insert(events[mod.name], name)
317 local registered = false
318 for _, s in pairs(revents) do
319 if s == name then
320 registered = true
321 break
322 end
323 end
324 if not registered then
325 table.insert(revents, name)
326 if eventsready then
327 event.listen(name, backgroundListener)
328 end
329 end
330end
331
332--[[
333Unregisters an event
334 @mod - calling module
335 @name - event name
336]]
337interface.unregisterEvent = function(mod, name)
338 if events[mod.name] then
339 for i, s in pairs(events[mod.name]) do
340 if s == name then
341 table.remove(events[mod.name], i)
342 break
343 end
344 end
345 local left = false
346 for _, t in pairs(events) do
347 for _, t2 in pairs(t) do
348 if t2 == name then
349 left = true
350 break
351 end
352 end
353 end
354 if not left then
355 for i, s in pairs(revents) do
356 if s == name then
357 table.remove(revents, i)
358 event.ignore(name, backgroundListener)
359 break
360 end
361 end
362 end
363 end
364end
365
366--[[
367Returns list of actions
368 @mod - calling module
369 @type:string or nil - action type filter
370 @target:string or nil - module filter
371 @name:string or nil - action name filter
372 RET: <list of actions, number of actions>
373]]
374interface.getActions = function(mod, type, target, name)
375 local ac = {}
376 local amount = 0
377 for m, t in pairs(actions) do
378 if (target and m == target) or (not target) then
379 for id, at in pairs(t) do
380 if (type and (at.type == type or at.type == "")) or (not type) then
381 if (name and at.name:find(name)) or (not name) then
382 ac[id] = at
383 amount = amount + 1
384 end
385 end
386 end
387 end
388 end
389 return ac, amount
390end
391
392--[[
393Returns table with registered components
394 @mod - calling module
395 @type - component type or nil
396 @force - show even disabled components
397 RET: <table with components>
398]]
399interface.getComponentList = function(mod, type, force)
400 local ret = {}
401 for _, t in pairs(components) do
402 if t.state then
403 if type then
404 if type == t.type then
405 table.insert(ret, t)
406 end
407 else
408 table.insert(ret, t)
409 end
410 end
411 end
412 return ret
413end
414
415--[[
416Returns a single component
417 @mod - calling module
418 @uid - uid of a component
419 @force - return even disabled component
420 RET: <component> or nil
421]]
422interface.getComponent = function(mod, uid, force)
423 if not uid then return nil end
424 for _, c in pairs(components) do
425 if c.id == uid then
426 if c.state or force then return c
427 else return nil end
428 end
429 end
430 return nil
431end
432
433--[[
434Searches for a registered components based on given address part
435 @mod - calling module
436 @pattern - address part
437 RET: <found components:table>
438]]
439interface.findComponents = function(mod, pattern)
440 local ret = {}
441 local pat = pattern and string.gsub(pattern, "-", "%%-") or ""
442 for _, t in pairs(components) do
443 if t.address:find(pat) then
444 table.insert(ret, t)
445 end
446 end
447 return ret
448end
449
450--[[
451Invokes given action
452 @mod - calling module
453 @id - action ID
454 @p1 - 1st parameter or nil
455 @p2 - 1nd parameter or nil
456 @silent:boolean - whether not to display error messages
457 RET: <action result> or nil
458]]
459interface.call = function(mod, id, p1, p2, silent)
460 local a = interface.actionDetails(mod, id)
461 if a then
462 local et = ""
463 if a.p1type and type(p1) ~= a.p1type then
464 et = type(p1) .. " ~= " .. a.p1type
465 elseif a.p2type and type(p2) ~= a.p2type then
466 if et:len() > 0 then
467 et = et .. ", "
468 end
469 et = et .. type(p2) .. " ~= " .. a.p2type
470 else
471 local s, r = pcall(a.exec, p1, p2)
472 if s then
473 return r
474 else
475 silentLog("interface.call", "action " .. tostring(id) .. " call failed: " .. r)
476 if not silent then
477 GMLmessageBox(gui, "Action " .. tostring(id) .. " call failed.", {"OK"})
478 end
479 return nil
480 end
481 end
482 if et:len() > 0 then
483 local m = "Wrong action parameters. ("
484 silentLog("interface.call", m .. et .. ")")
485 if not silent then
486 GMLmessageBox(gui, m .. et .. ")", {"OK"})
487 end
488 end
489 else
490 silentLog("interface.call", "action " .. tostring(id) .. " wasn't found")
491 if not silent then
492 GMLmessageBox(gui, "Couldn't find action " .. tostring(id) .. "!", {"OK"})
493 end
494 end
495 return nil
496end
497
498--[[
499Invokes given action by name
500 @mod - calling module
501 @id - action name
502 @p1 - 1st parameter or nil
503 @p2 - 1nd parameter or nil
504 @silent:boolean - whether not to display error messages
505 RET: <action result> or nil
506]]
507interface.callByName = function(mod, name, p1, p2, silent)
508 local found = nil
509 for _, t in pairs(actions) do
510 for i, a in pairs(t) do
511 if a.name == name then
512 found = i
513 break
514 end
515 end
516 if found ~= nil then break end
517 end
518 if found ~= nil then
519 return interface.call(mod, found, p1, p2, silent)
520 else
521 silentLog("interface.call", "action \"" .. name .. "\" wasn't found")
522 if not silent then
523 GMLmessageBox(gui, "Couldn't find action with name " .. name .. "!", {"OK"})
524 end
525 return nil
526 end
527end
528
529--[[
530Returns table of given action
531 @mod - calling module
532 @id - action ID
533 RET: <action table> or nil
534]]
535interface.actionDetails = function(mod, id)
536 for _, t in pairs(actions) do
537 for i, a in pairs(t) do
538 if i == id then
539 return a
540 end
541 end
542 end
543 return nil
544end
545
546--[[
547Adds new log
548 @mod - calling module
549 @msg - message do display
550]]
551interface.log = function(mod, msg)
552 silentLog(mod.name, msg)
553end
554
555--[[
556Displays message box
557 @mod - calling module
558 @message - message to display
559 @buttons - table with buttons
560 RET: <selected button>
561]]
562interface.messageBox = function(mod, message, buttons)
563 local r, e = pcall(GMLmessageBox, gui, message, buttons)
564 if r then
565 return e
566 else
567 silentLog("interface.messageBox", "couldn't display message: " .. e)
568 return nil
569 end
570end
571
572--[[
573Displays dialog selection window
574 @mod - calling module
575 @type:string or nil - action category
576 @target:string or nil - module name filter
577 @fill:table or nil - table with current action settings ({[id:number],[p1],[p2]})
578 @hidden:boolean - whether to display hidden actions
579 RET:<user choice (see the fill parameter))> or nil
580]]
581interface.actionDialog = function(mod, typee, target, fill, hidden)
582 local ac = interface.getActions(mod, typee, target)
583 local sublist = nil
584 local ll = {}
585 local box = nil
586 local rs = {}
587 local ret = {}
588
589 local function rebuild(l)
590 ll = {}
591 for _, t in pairs(l) do
592 if (hidden and t.hidden) or not t.hidden then
593 table.insert(ll, t.name .. " (" .. t.type:upper() .. ")")
594 end
595 end
596 table.sort(ll)
597 box:updateList(ll)
598 end
599 local function update(id, t)
600 if not id or not t then
601 ret.id = nil
602 for i = 1, 7 do rs[i]:hide() end
603 return
604 end
605 ret.id = id
606 rs[1].text = t.desc:sub(1, 39)
607 rs[1]:show()
608 rs[1]:draw()
609 rs[2]:show()
610 rs[3].text = tostring(id)
611 rs[3]:show()
612 rs[3]:draw()
613 if t.p1type then
614 rs[4].text = string.sub(t.p1desc .. "(" .. t.p1type .. ")", 1, 39)
615 rs[4]:show()
616 rs[4]:draw()
617 rs[5]:show()
618 rs[5]:draw()
619 else
620 rs[4]:hide()
621 rs[5]:hide()
622 end
623 if t.p2type then
624 rs[6].text = string.sub(t.p2desc .. "(" .. t.p2type .. ")", 1, 39)
625 rs[6]:show()
626 rs[6]:draw()
627 rs[7]:show()
628 rs[7]:draw()
629 else
630 rs[6]:hide()
631 rs[7]:hide()
632 end
633 end
634 local function doRefresh(l)
635 local selected = box:getSelected():match("^(.*) %(")
636 if selected then
637 for i, t in pairs(l) do
638 if t.name == selected then
639 update(i, t)
640 return
641 end
642 end
643 end
644 for i = 1, 7 do rs[i]:hide() end
645 end
646 local function refresh()
647 doRefresh(sublist or ac)
648 end
649
650 local agui = gml.create("center", "center", 80, 24)
651 agui.style = interface.getStyle(mod)
652 agui:addLabel("center", 1, 24, "Action selection window")
653 rs[1] = agui:addLabel(35, 6, 40, "")
654 rs[2] = agui:addLabel(35, 8, 15, "Identifier:")
655 rs[4] = agui:addLabel(35, 11, 40, "")
656 rs[6] = agui:addLabel(35, 14, 40, "")
657 local search = agui:addTextField(2, 4, 14)
658 agui:addButton(17, 4, 12, 1, "Search", function()
659 if search.text:len() > 0 then
660 sublist = {}
661 for i, t in pairs(ac) do
662 if t.name:find(search.text) then
663 sublist[i] = t
664 end
665 end
666 else
667 sublist = nil
668 end
669 rebuild(sublist or ac)
670 refresh()
671 end)
672 box = agui:addListBox(2, 6, 28, 14, {})
673 box.onChange = refresh
674 rs[3] = agui:addLabel(51, 8, 10, "")
675 rs[5] = agui:addTextField(38, 12, 20)
676 rs[5].visible = false
677 rs[7] = agui:addTextField(38, 15, 20)
678 rs[7].visible = false
679 agui:addButton(3, 22, 14, 1, "Clear", function()
680 ret.id = nil
681 update()
682 end)
683 agui:addButton(63, 22, 14, 1, "Cancel", function()
684 agui:close()
685 ret = fill
686 end)
687 agui:addButton(47, 22, 14, 1, "Apply", function()
688 if not ret.id then
689 agui:close()
690 return
691 end
692 local a = interface.actionDetails(nil, tonumber(rs[3].text))
693 if a then
694 if a.p1type then
695 if a.p1type == "number" then
696 local n = tonumber(rs[5].text)
697 if not n then
698 GMLmessageBox(gui, "First parameter must be a number.", {"OK"})
699 return
700 end
701 ret.p1 = n
702 elseif rs[5].text:len() == 0 then
703 GMLmessageBox(gui, "First parameter cannot be empty.", {"OK"})
704 return
705 else
706 ret.p1 = rs[5].text
707 end
708 end
709 if a.p2type then
710 if a.p2type == "number" then
711 local n = tonumber(rs[7].text)
712 if not n then
713 GMLmessageBox(gui, "Second parameter must be a number.", {"OK"})
714 return
715 end
716 ret.p2 = n
717 elseif rs[7].text:len() == 0 then
718 GMLmessageBox(gui, "Second parameter cannot be empty..", {"OK"})
719 return
720 else
721 ret.p2 = rs[7].text
722 end
723 end
724 agui:close()
725 end
726 end)
727 local function firstFill(id, t)
728 if not id or not t then
729 ret.id = nil
730 for i = 1, 7 do rs[i].hidden = true end
731 return
732 end
733 ret.id = id
734 rs[1].text = t.desc:sub(1, 39)
735 rs[3].text = tostring(id)
736 if t.p1type then
737 rs[4].text = string.sub(t.p1desc .. "(" .. t.p1type .. ")", 1, 39)
738 else
739 rs[4].hidden = true
740 rs[5].hidden = true
741 end
742 if t.p2type then
743 rs[6].text = string.sub(t.p2desc .. "(" .. t.p2type .. ")", 1, 39)
744 else
745 rs[6].hidden = true
746 rs[7].hidden = true
747 end
748 end
749 if fill and fill.id then
750 local a = interface.actionDetails(nil, fill.id)
751 if a then
752 if fill.p1 and type(fill.p1) == "number" then
753 rs[5].text = tostring(fill.p1)
754 else
755 rs[5].text = fill.p1 or ""
756 end
757 if fill.p2 and type(fill.p2) == "number" then
758 rs[7].text = tostring(fill.p2)
759 else
760 rs[7].text = fill.p2 or ""
761 end
762 firstFill(fill.id, a)
763 else
764 firstFill()
765 GMLmessageBox(gui, "Couldn't find action with given ID.", {"OK"})
766 end
767 else
768 firstFill()
769 end
770 rebuild(ac)
771 agui:run()
772 return (ret and ret.id) and ret or nil
773end
774
775--[[
776Displays component selection dialog window
777 @mod - calling module
778 @typee - component type filter
779 @force - include disabled compoennts
780 RET: <component UID> or nil
781]]
782interface.componentDialog = function(mod, typee, force)
783 local cl = interface.getComponentList(mod, typee, force)
784 local box, list, chosenAddr, chosenUid = {}, {}, nil, nil
785 local ret = nil
786
787 local function refreshList()
788 list = {}
789 for _, t in pairs(cl) do
790 local s = string.format("[%s] %s %s (%s, %s, %s)", t.id, t.name and t.name:sub(1, 20) or "", t.address, t.x and tostring(t.x) or "", t.y and tostring(t.y) or "", t.z and tostring(t.z) or "")
791 table.insert(list, s)
792 end
793 box:updateList(list)
794 end
795 local function update()
796 local sel = box:getSelected()
797 if sel then
798 local first = sel:find("%[")
799 local last = sel:find("%]")
800 if first and last then
801 local found = sel:sub(first + 1, last - 1)
802 local comp = interface.getComponent(mod, found, force)
803 if comp then
804 chosenUid.text = found
805 chosenAddr.text = comp.address
806 ret = found
807 else
808 chosenUid.text = "<not found>"
809 chosenAddr.text = "<not found>"
810 ret = nil
811 end
812 chosenUid:draw()
813 chosenAddr:draw()
814 end
815 end
816 end
817 local dgui = gml.create("center", "center", 110, 27)
818 dgui.style = gui.style
819 dgui:addLabel("center", 1, 27, "Component selection window")
820 box = dgui:addListBox(2, 3, 104, 15, {})
821 local old = box.onClick
822 box.onClick = function(...)
823 old(...)
824 update()
825 end
826 dgui:addLabel(5, 20, 12, "Chosen UID:")
827 dgui:addLabel(5, 21, 15, "Chosen address:")
828 chosenUid = dgui:addLabel(21, 20, 10, "")
829 chosenAddr = dgui:addLabel(21, 21, 40, "")
830 dgui:addButton(90, 24, 14, 1, "Cancel", function()
831 ret = nil
832 dgui:close()
833 end)
834 dgui:addButton(74, 24, 14, 1, "Apply", function()
835 dgui:close()
836 end)
837 refreshList()
838 dgui:run()
839 return ret
840end
841
842--[[
843Dispays color picker dialog window
844 @mod - calling module
845 @hex:boolean - whether to return color in hex format
846 @api:boolean - whether to return color in api colors format
847 @name:boolean - whether to return color as text
848 RET:{[hex], [colors]} or nil
849]]
850interface.colorDialog = function(mod, hex, api, name)
851 local color = nil
852 local rettab = {}
853 local bar = nil
854 local eqs = {
855 [0] = 0xFFFFFF,
856 [1] = 0xFFA500,
857 [2] = 0xFF00FF,
858 [3] = 0xADD8E6,
859 [4] = 0xFFFF00,
860 [5] = 0x00FF00,
861 [6] = 0xFFC0CB,
862 [7] = 0x808080,
863 [8] = 0xC0C0C0,
864 [9] = 0x00FFFF,
865 [10] = 0x800080,
866 [11] = 0x0000FF,
867 [12] = 0xA52A2A,
868 [13] = 0x008000,
869 [14] = 0xFF0000,
870 [15] = 0x000000
871 }
872 local function updateBar(new)
873 color = new
874 bar.color = eqs[new]
875 bar:draw()
876 end
877 local cgui = gml.create("center", "center", 32, 25)
878 cgui.style = gui.style
879 cgui:addLabel("center", 1, 14, "Choose a color")
880 for i = 0, 15 do
881 local tmp = cgui:addLabel(3, 4 + i, 12, colors[i])
882 tmp.onClick = function() updateBar(i) end
883 end
884 bar = interface.template(mod, cgui, 20, 4, 3, 16)
885 bar.draw = function(t)
886 if t.color then
887 t.renderTarget.setBackground(t.color)
888 t.renderTarget.fill(t.gui.posX + t.posX - 1, t.gui.posY + t.posY - 1, t.width, t.height, " ")
889 end
890 end
891 cgui:addButton(1, 23, 14, 1, "Apply", function()
892 if hex then table.insert(rettab, eqs[color]) end
893 if api then table.insert(rettab, color) end
894 if name then table.insert(rettab, colors[i]) end
895 cgui:close()
896 end)
897 cgui:addButton(16, 23, 14, 1, "Cancel", function()
898 rettab = nil
899 cgui:close()
900 end)
901 cgui:run()
902 return rettab
903end
904
905--[[
906Creates a template for a new component
907 @mod - calling module
908 @target - target GUI
909 @x - X position
910 @y - Y position
911 @w - element width
912 @h - element height
913 RET: <template>
914]]
915interface.template = function(mod, target, x, y, w, h)
916 local temp = {
917 visible = false,
918 hidden = false,
919 gui = target,
920 style = target.style,
921 focusable = false,
922 type = "label",
923 renderTarget = target.renderTarget,
924 horizontal = isHorizontal,
925 bgcolor = GMLextractProperty(target, GMLgetAppliedStyles(target), "fill-color-bg")
926 }
927 temp.posX = x + 1
928 temp.posY = y + 1
929 temp.width = w
930 temp.height = h
931 temp.contains = GMLcontains
932 temp.isHidden = function() return false end
933 temp.draw = function() end
934 target:addComponent(temp)
935 return temp
936end
937
938--[[
939Returns default module directory
940 @mod - calling module
941 RET: absolute path to module directory
942]]
943interface.getConfigDirectory = function(mod)
944 local dir = fs.concat(configDir .. "/modules", mod.name)
945 if not fs.isDirectory(dir) then fs.makeDirectory(dir) end
946 return dir
947end
948
949--[[
950Returns encryption key
951 @mod - calling module
952 RET: encryption key in binary format
953]]
954interface.secretKey = function(mod)
955 return token
956end
957
958--[[
959Returns theme used by server
960 @mod - calling module
961 RET: style table
962]]
963interface.getStyle = function(mod)
964 return gui.style
965end
966
967-- # Funkcje pomocnicze Gui
968GMLmessageBox = function(target, message, buttons)
969 local buttons = buttons or {"cancel", "ok"}
970 local choice
971 local lines = {}
972 message:gsub("([^\n]+)", function(line) lines[#lines+1] = line end)
973 local i = 1
974 while i <= #lines do
975 if #lines[i] > 46 then
976 local s, rs = lines[i], lines[i]:reverse()
977 local pos =- 26
978 local prev = 1
979 while #s > prev + 45 do
980 local space = rs:find(" ", pos)
981 if space then
982 table.insert(lines, i, s:sub(prev, #s - space))
983 prev = #s - space + 2
984 pos =- (#s - space + 48)
985 else
986 table.insert(lines, i, s:sub(prev, prev + 45))
987 prev = prev + 46
988 pos = pos - 46
989 end
990 i = i + 1
991 end
992 lines[i] = s:sub(prev)
993 end
994 i = i + 1
995 end
996
997 local gui = gml.create("center", "center", 50, 6 + #lines, gpu)
998 gui.style = target.style
999 local labels = {}
1000 for i = 1, #lines do
1001 labels[i] = gui:addLabel(2, 1 + i, 46, lines[i])
1002 end
1003 local buttonObjs = {}
1004 local xpos = 2
1005 for i = 1, #buttons do
1006 if i == #buttons then xpos =- 2 end
1007 buttonObjs[i]=gui:addButton(xpos, -2, #buttons[i] + 2, 1, buttons[i], function() choice = buttons[i] gui.close() end)
1008 xpos = xpos + #buttons[i] + 3
1009 end
1010
1011 gui:changeFocusTo(buttonObjs[#buttonObjs])
1012 gui:run()
1013 return choice
1014end
1015
1016GMLcontains = function(element,x,y)
1017 local ex, ey, ew, eh = element.posX, element.posY, element.width, element.height
1018 return x >= ex and x <= ex + ew - 1 and y >= ey and y <= ey + eh - 1
1019end
1020
1021GMLgetAppliedStyles = function(element)
1022 local styleRoot = element.style
1023 assert(styleRoot)
1024
1025 local depth, state, class, elementType = element.renderTarget.getDepth(), element.state or "*", element.class or "*", element.type
1026
1027 local nodes = {styleRoot}
1028 local function filterDown(nodes, key)
1029 local newNodes = {}
1030 for i = 1, #nodes do
1031 if key ~= "*" and nodes[i][key] then
1032 newNodes[#newNodes + 1] = nodes[i][key]
1033 end
1034 if nodes[i]["*"] then
1035 newNodes[#newNodes + 1] = nodes[i]["*"]
1036 end
1037 end
1038 return newNodes
1039 end
1040 nodes = filterDown(nodes, depth)
1041 nodes = filterDown(nodes, state)
1042 nodes = filterDown(nodes, class)
1043 nodes = filterDown(nodes, elementType)
1044 return nodes
1045end
1046
1047GMLextractProperty = function(element, styles, property)
1048 if element[property] then
1049 return element[property]
1050 end
1051 for j = 1, #styles do
1052 local v = styles[j][property]
1053 if v ~= nil then
1054 return v
1055 end
1056 end
1057end
1058
1059GMLextractProperties = function(element, styles, ...)
1060 local props = {...}
1061 local vals = {}
1062 for i = 1, #props do
1063 vals[#vals + 1] = extractProperty(element, styles, props[i])
1064 if #vals ~= i then
1065 for k, v in pairs(styles[1]) do print('"' .. k .. '"', v, k == props[i] and "<-----!!!" or "") end
1066 error("Could not locate value for style property " .. props[i] .. "!")
1067 end
1068 end
1069 return table.unpack(vals)
1070end
1071
1072GMLfindStyleProperties = function(element,...)
1073 local props = {...}
1074 local nodes = GMLgetAppliedStyles(element)
1075 return GMLextractProperties(element, nodes, ...)
1076end
1077
1078GMLcalcBody = function(element)
1079 local x, y, w, h = element.posX, element.posY, element.width, element.height
1080 local border, borderTop, borderBottom, borderLeft, borderRight =
1081 GMLfindStyleProperties(element, "border", "border-top", "border-bottom", "border-left", "border-right")
1082
1083 if border then
1084 if borderTop then
1085 y = y + 1
1086 h = h - 1
1087 end
1088 if borderBottom then
1089 h = h - 1
1090 end
1091 if borderLeft then
1092 x = x + 1
1093 w = w - 1
1094 end
1095 if borderRight then
1096 w = w - 1
1097 end
1098 end
1099 return x, y, w, h
1100end
1101
1102local function addBar(target, x, y, length, isHorizontal)
1103 local bar = {
1104 visible = false,
1105 hidden = false,
1106 gui = target,
1107 style = target.style,
1108 focusable = false,
1109 type = "label",
1110 renderTarget = target.renderTarget,
1111 horizontal = isHorizontal,
1112 bgcolor = GMLextractProperty(target, GMLgetAppliedStyles(target), "fill-color-bg")
1113 }
1114 bar.posX = x
1115 bar.posY = y
1116 bar.width = isHorizontal and length or 1
1117 bar.height = isHorizontal and 1 or length
1118 bar.contains = GMLcontains
1119 bar.isHidden = function() return false end
1120 bar.draw = function(t)
1121 t.renderTarget.setBackground(t.bgcolor)
1122 t.renderTarget.setForeground(0xffffff)
1123 if t.horizontal then
1124 t.renderTarget.set(t.posX + 1, t.posY + 1, string.rep(uni.char(0x2550), t.width))
1125 else
1126 for i = 1, t.height do
1127 t.renderTarget.set(t.posX + 1, t.posY + i, uni.char(0x2551))
1128 end
1129 end
1130 end
1131 target:addComponent(bar)
1132 return bar
1133end
1134
1135local function addSymbol(target, x, y, code)
1136 local symbol = {
1137 visible = false,
1138 hidden = false,
1139 gui = target,
1140 style = target.style,
1141 focusable = false,
1142 type = "label",
1143 renderTarget = target.renderTarget,
1144 bgcolor = GMLextractProperty(target, GMLgetAppliedStyles(target), "fill-color-bg"),
1145 code = code,
1146 posX = x,
1147 posY = y,
1148 width = 1,
1149 height = 1,
1150 contains = GMLcontains,
1151 isHidden = function() return false end
1152 }
1153 symbol.draw = function(t)
1154 t.renderTarget.setBackground(t.bgcolor)
1155 t.renderTarget.setForeground(0xffffff)
1156 t.renderTarget.set(t.posX, t.posY, uni.char(t.code))
1157 end
1158 target:addComponent(symbol)
1159 return symbol
1160end
1161
1162local function addTitle(target, posX, posY)
1163 local title = {
1164 visible = false,
1165 hidden = false,
1166 gui = target,
1167 style = target.style,
1168 focusable = false,
1169 type = "label",
1170 renderTarget = target.renderTarget,
1171 posX = posX,
1172 posY = posY,
1173 width = 15,
1174 height = 5,
1175 contains = GMLcontains,
1176 isHidden = function() return false end
1177 }
1178 title.draw = function(t)
1179 t.renderTarget.setBackground(0x00a6ff)
1180 t.renderTarget.fill(t.posX, t.posY, 5, 1, ' ') --t
1181 t.renderTarget.fill(t.posX + 2, t.posY + 1, 1, 4, ' ')
1182 t.renderTarget.fill(t.posX + 9, t.posY, 4, 1, ' ') --g
1183 t.renderTarget.fill(t.posX + 9, t.posY + 4, 4, 1, ' ')
1184 t.renderTarget.fill(t.posX + 10, t.posY + 2, 3, 1, ' ')
1185 t.renderTarget.set(t.posX + 8, t.posY + 1, ' ')
1186 t.renderTarget.set(t.posX + 8, t.posY + 3, ' ')
1187 t.renderTarget.set(t.posX + 7, t.posY + 2, ' ')
1188 t.renderTarget.set(t.posX + 12, t.posY + 3, ' ')
1189 end
1190 target:addComponent(title)
1191 return title
1192end
1193
1194-- # Other functions
1195local function flushLog()
1196 local file = io.open("/tmp/tg.log", "a")
1197 if file then
1198 file:write(intlog)
1199 file:close()
1200 intlog = ""
1201 end
1202end
1203
1204local function generateUid()
1205 local template ='xyxyxy'
1206 return string.gsub(template, '[xy]', function (c)
1207 local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb)
1208 return string.format('%x', v)
1209 end)
1210end
1211
1212local function generateComponentUid()
1213 while true do
1214 local uid = generateUid()
1215 local unique = true
1216 for _, c in pairs(components) do
1217 if c.id == uid then
1218 unique = false
1219 break
1220 end
1221 end
1222
1223 if unique then return uid end
1224 end
1225end
1226
1227silentLog = function(source, description, disableTimer, color)
1228 local timer = disableTimer and "" or (os.date():sub(-8) .. " - ")
1229 local text = timer .. source
1230 if description ~= nil and description:len() > 0 then
1231 text = text .. ": " .. description
1232 end
1233 intlog = intlog .. text .. "\n"
1234 loglines = loglines + 1
1235 if loglines > 20 then
1236 flushLog()
1237 loglines = 0
1238 end
1239 table.insert(lastlog, text)
1240 if #lastlog > 10 then table.remove(lastlog, 1) end
1241 return text
1242end
1243
1244local function internalLog(source, description, disableTimer, color)
1245 local prev = nil
1246 if color then
1247 prev = component.gpu.setForeground(color)
1248 end
1249 print(silentLog(source, description, disableTimer, color))
1250 if color then
1251 component.gpu.setForeground(prev)
1252 end
1253end
1254
1255local function isPasswordValid(plain)
1256 return data.sha256(plain) == passwd
1257end
1258
1259-- # Configuration
1260local save = {}
1261
1262function save.log(silent, ...)
1263 if silent then
1264 silentLog(...)
1265 save.err()
1266 else
1267 internalLog(...)
1268 end
1269end
1270
1271function save.err()
1272 GMLmessageBox(gui, "An error occurred while saving settings, check the logs.", {"OK"})
1273end
1274
1275function save.settings(silent)
1276 local r, s = pcall(serial.serialize, settings)
1277 if r then
1278 local f, e = io.open("/etc/the_guard/config.conf", "w")
1279 if f then
1280 f:write(s)
1281 f:close()
1282 else
1283 save.log(silent, "save", "error while opening settings file: " .. e)
1284 return false
1285 end
1286 else
1287 save.log(silent, "save", "cannot serialize settings: " .. s)
1288 return false
1289 end
1290 return true
1291end
1292
1293function save.modules(silent)
1294 local out = {}
1295 for z, t in pairs(modules) do
1296 out[z] = {file = t.file}
1297 end
1298 local r, s = pcall(serial.serialize, out)
1299 if r then
1300 local f, e = io.open("/etc/the_guard/modules.conf", "w")
1301 if f then
1302 f:write(s)
1303 f:close()
1304 else
1305 save.log(silent, "save", "error while opening modules file: " .. e)
1306 return false
1307 end
1308 else
1309 save.log(silent, "save", "cannot serialize modules: " .. s)
1310 return false
1311 end
1312 return true
1313end
1314
1315function save.components(silent)
1316 local r, s = pcall(serial.serialize, components)
1317 if r then
1318 local f, e = io.open("/etc/the_guard/components.conf", "w")
1319 if f then
1320 f:write(s)
1321 f:close()
1322 else
1323 save.log(silent, "save", "error while opening components file: " .. e)
1324 return false
1325 end
1326 else
1327 save.log(silent, "save", "cannot serialize components: " .. s)
1328 return false
1329 end
1330 return true
1331end
1332
1333function save.passwd(silent)
1334 local output = data.encode64(passwd)
1335 local f, e = io.open("/etc/the_guard/passwd.bin", "wb")
1336 if f then
1337 f:write(output)
1338 f:close()
1339 else
1340 save.log(silent, "save", "cannot open master password file: " .. e)
1341 return false
1342 end
1343 return true
1344end
1345
1346local function saveConfig()
1347 if not save.settings() then
1348 internalLog("save", "couldn't save settings", false, 0xff0000)
1349 end
1350 if not save.modules() then
1351 internalLog("save", "couldn't save modules' settings", false, 0xff0000)
1352 end
1353 if not save.components() then
1354 internalLog("save", "couldn't save components' settings", false, 0xff0000)
1355 end
1356 if not save.passwd() then
1357 internalLog("save", "couldn't save master password", false, 0xff0000)
1358 end
1359end
1360
1361local function loadConfig()
1362 local function checkSettings()
1363 local dirty = false
1364 if not settings.port then
1365 settings.port = math.random(1000, 50000)
1366 dirty = true
1367 end
1368 if not settings.backupPort then
1369 settings.backupPort = math.random(1000, 50000)
1370 dirty = true
1371 end
1372 if not settings.debugMode then
1373 settings.debugMode = false
1374 dirty = true
1375 end
1376 if not settings.dark then
1377 settings.dark = false
1378 dirty = true
1379 end
1380 if dirty then
1381 save.settings(true)
1382 end
1383 end
1384
1385 local function checkModules()
1386 local counter = 0
1387 for i, m in pairs(modules) do
1388 local added = true
1389 if type(i) ~= "number" then
1390 internalLog("checkModules", "wrong zone identifier")
1391 modules[i] = nil
1392 added = false
1393 else
1394 if type(m.file) == "string" then
1395 local path = m.file
1396 if not (fs.exists(path) and not fs.isDirectory(path)) then
1397 internalLog("checkModules", "file doesn't exist")
1398 modules[i] = nil
1399 added = false
1400 end
1401 else
1402 internalLog("checkModules", "missing file name")
1403 modules[i] = nil
1404 added = false
1405 end
1406 end
1407 if added then counter = counter + 1 end
1408 end
1409 internalLog("Checked " .. tostring(counter) .. " module(s)", "", true)
1410 end
1411
1412 local function checkComponents()
1413 local counter = 0
1414 for i, c in pairs(components) do
1415 if type(c["id"]) ~= "string" then
1416 internalLog("checkComponents", "wrong id")
1417 components[i]["id"] = generateComponentUid()
1418 end
1419 if type(c["address"]) == "string" then
1420 if component.proxy(c["address"]) then
1421 if type(c["type"]) ~= "string" then
1422 internalLog("checkComponents", "wrong type")
1423 components[i]["type"] = ""
1424 end
1425 if type(c["state"]) ~= "boolean" then
1426 internalLog("checkComponents", "wrong state")
1427 components[i]["state"] = false
1428 end
1429 if not (type(c["x"]) == "number" or type(c["x"]) == "nil") then
1430 internalLog("checkComponents", "wrong x coord")
1431 components[i]["x"] = nil
1432 end
1433 if not (type(c["y"]) == "number" or type(c["y"]) == "nil") then
1434 internalLog("checkComponents", "wrong y coord")
1435 components[i]["y"] = nil
1436 end
1437 if not (type(c["z"]) == "number" or type(c["z"]) == "nil") then
1438 internalLog("checkComponents", "wrong z coord")
1439 components[i]["z"] = nil
1440 end
1441 else
1442 internalLog("checkComponents", "device " .. c["address"] .. " is offline")
1443 components[i].state = false
1444 end
1445 else
1446 internalLog("checkComponents", "wrong address format")
1447 if type(i) == "number" then
1448 table.remove(components, i)
1449 else
1450 components[i] = nil
1451 end
1452 end
1453 counter = counter + 1
1454 end
1455 internalLog("Checked " .. tostring(counter) .. " components(s)", "", true)
1456 end
1457
1458 local function checkPassword()
1459 if not passwd or passwd:len() == 0 then
1460 local prev = component.gpu.setForeground(0xff0000)
1461 print("No master password set. Enter a new password")
1462 component.gpu.setForeground(prev)
1463 local i1, i2 = "", ""
1464 local text = require("text")
1465 repeat
1466 io.write("#> ")
1467 i1 = term.read(nil, nil, nil, "*")
1468 i1 = text.trim(i1)
1469 print("Repeat password")
1470 io.write("#> ")
1471 i2 = term.read(nil, nil, nil, "*")
1472 i2 = text.trim(i2)
1473 if i1 ~= i2 then
1474 local prev = component.gpu.setForeground(0xffff00)
1475 print("Passwords don't match. Try again.")
1476 component.gpu.setForeground(prev)
1477 end
1478 until i1 == i2
1479 passwd = data.sha256(i1)
1480 local f, e = io.open("/etc/the_guard/passwd.bin", "wb")
1481 if f then
1482 f:write(data.encode64(passwd))
1483 f:close()
1484 else
1485 internalLog("passwd", "An error occurred while saving password: " .. e)
1486 return false
1487 end
1488 end
1489
1490 return true
1491 end
1492
1493 local dir = "/etc/the_guard/"
1494 internalLog("Loading settings", "", true)
1495 local path = fs.concat(dir, "/config.conf")
1496 if fs.exists(path) and not fs.isDirectory(path) then
1497 local f, e = io.open(path, "r")
1498 if f then
1499 local s = serial.unserialize(f:read("*a"))
1500 if s then
1501 settings = s
1502 else
1503 internalLog("loadConfig", "settings file is corrupted or empty", false, 0xffff00)
1504 end
1505 f:close()
1506 else
1507 internalLog("loadConfig", "settings file error: " .. e, false, 0xff0000)
1508 return false
1509 end
1510 else
1511 internalLog("loadConfig", "settings file missing, loading defaults")
1512 end
1513 checkSettings()
1514
1515 path = fs.concat(dir, "modules.conf")
1516 if fs.exists(path) and not fs.isDirectory(path) then
1517 local f, e = io.open(path, "r")
1518 if f then
1519 local s = serial.unserialize(f:read("*a"))
1520 if s then
1521 modules = s
1522 else
1523 internalLog("loadConfig", "modules file is corrupted or empty", false, 0xffff00)
1524 end
1525 f:close()
1526 else
1527 internalLog("loadConfig", "modules file error: " .. e, false, 0xff0000)
1528 return false
1529 end
1530 else
1531 internalLog("loadConfig", "modules file missing, loading defauls")
1532 end
1533 checkModules()
1534
1535 path = fs.concat(dir, "components.conf")
1536 if fs.exists(path) and not fs.isDirectory(path) then
1537 local f, e = io.open(path, "r")
1538 if f then
1539 local s = serial.unserialize(f:read("*a"))
1540 if s then
1541 components = s
1542 else
1543 internaLog("loadConfig", "components file is corrupted or empty", false, 0xffff00)
1544 end
1545 f:close()
1546 else
1547 internalLog("loadConfig", "components file error: " .. e, false, 0xff0000)
1548 return false
1549 end
1550 else
1551 internalLog("loadConfig", "components file missing, loading defaults")
1552 end
1553 checkComponents()
1554
1555 path = fs.concat(dir, "passwd.bin")
1556 if fs.exists(path) and not fs.isDirectory(path) then
1557 local f, e = io.open(path, "rb")
1558 if f then
1559 passwd = data.decode64(f:read("*a"))
1560 f:close()
1561 else
1562 internalLog("passwd", "couldn't open password file: " .. e)
1563 f:close()
1564 return false
1565 end
1566 else
1567 internalLog("passwd", "password file missing, loading defaults")
1568 end
1569 if not checkPassword() then return false end
1570
1571 return true
1572end
1573
1574-- # Buttons' functions
1575local function passwordPrompt()
1576 local status = false
1577 local function insertTextTF(tf, text)
1578 if tf.selectEnd ~= 0 then
1579 tf:removeSelected()
1580 end
1581 tf.real = tf.real:sub(1, tf.cursorIndex - 1) .. text .. tf.real:sub(tf.cursorIndex)
1582 tf.text = string.rep("*", #tf.real)
1583 tf.cursorIndex = tf.cursorIndex + #text
1584 if tf.cursorIndex - tf.scrollIndex + 1 > tf.width then
1585 local ts = tf.scrollIndex + math.floor(tf.width / 3)
1586 if tf.cursorIndex - ts + 1 > tf.width then
1587 ts = tf.cursorIndex - tf.width + math.floor(tf.width / 3)
1588 end
1589 tf.scrollIndex = ts
1590 end
1591 end
1592 local pgui = gml.create("center", "center", 50, 8)
1593 if gui then
1594 pgui.style = gui.style
1595 end
1596 pgui:addLabel("center", 1, 18, "Enter a password:")
1597 local field = pgui:addTextField("center", 3, 30)
1598 field.real = ""
1599 field.insertText = insertTextTF
1600 pgui:addButton(20, 5, 12, 1, "OK", function()
1601 if isPasswordValid(field.real) then
1602 status = true
1603 end
1604 pgui:close()
1605 end)
1606 pgui:addButton(34, 5, 12, 1, "Cancel", function() pgui:close() end)
1607 pgui:run()
1608 return status
1609end
1610
1611local function addCreator(address, typee)
1612 local uid = generateComponentUid()
1613 local agui = gml.create("center", "center", 54, 16)
1614 agui.style = gui.style
1615 agui:addLabel("center", 1, 24, "New component wizard")
1616 agui:addLabel(2, 3, 20, "UID: " .. uid)
1617 agui:addLabel(2, 4, 50, "Address: " .. address)
1618 agui:addLabel(2, 5, 48, "Type: " .. typee)
1619 agui:addLabel(2, 6, 7, "Name:")
1620 agui:addLabel(2, 7, 9, "Status:")
1621 agui:addLabel(2, 9, 17, "X coordinate:")
1622 agui:addLabel(2, 10, 17, "Y coordinate:")
1623 agui:addLabel(2, 11, 17, "Z coordinate:")
1624 local name = agui:addTextField(11, 6, 22)
1625 local cx = agui:addTextField(20, 9, 10)
1626 local cy = agui:addTextField(20, 10, 10)
1627 local cz = agui:addTextField(20, 11, 10)
1628 local button = agui:addButton(11, 7, 13, 1, "enabled", function(self)
1629 if self.status then
1630 self.text = "disabled"
1631 self.status = false
1632 self:draw()
1633 else
1634 self.text = "enabled"
1635 self.status = true
1636 self:draw()
1637 end
1638 end)
1639 button.status = true
1640 agui:addButton(20, 14, 14, 1, "Apply", function()
1641 local nx = tonumber(cx.text)
1642 local ny = tonumber(cy.text)
1643 local nz = tonumber(cz.text)
1644 if not nx and cx.text:len() > 0 then
1645 GMLmessageBox(gui, "The X coordiante is incorrect.", {"OK"})
1646 elseif not ny and cy.text:len() > 0 then
1647 GMLmessageBox(gui, "The Y coordinate is incorrect.", {"OK"})
1648 elseif not nz and cz.text:len() > 0 then
1649 GMLmessageBox(gui, "The Z coordinate is incorrect.", {"OK"})
1650 elseif name.text:len() > 20 then
1651 GMLmessageBox(gui, "Component name cannot be longer than 20 characters.", {"OK"})
1652 else
1653 local t = {
1654 id = uid,
1655 address = address,
1656 type = typee,
1657 name = name.text,
1658 state = button.status,
1659 x = nx,
1660 y = ny,
1661 z = nz
1662 }
1663 table.insert(components, t)
1664 save.components(true)
1665 agui:close()
1666 end
1667 end)
1668 agui:addButton(36, 14, 14, 1, "Cancel", function() agui:close() end)
1669 agui:run()
1670end
1671
1672local function bNewComponent(returnOnly, typeFilter)
1673 local selected = nil
1674 local ngui, list, tab = nil, nil, nil
1675 local function isAdded(address)
1676 for _, t in pairs(components) do
1677 local matches = typeFilter and t.type == typeFilter or true
1678 if t.address == address and matches then return true end
1679 end
1680 return false
1681 end
1682 local function reloadList()
1683 tab = {}
1684 for addr, name in component.list() do
1685 if not isAdded(addr) then
1686 table.insert(tab, addr .. " " .. name)
1687 end
1688 end
1689 end
1690 local function addComponent()
1691 local addr, typ = list:getSelected():match("^(%x+%-%x+%-%x+%-%x+%-%x+)%s%s%s(.+)")
1692 if addr and typ then
1693 selected = addr
1694 if returnOnly then
1695 ngui:close()
1696 else
1697 addCreator(addr, typ)
1698 reloadList()
1699 list:updateList(tab)
1700 end
1701 end
1702 end
1703 reloadList()
1704 ngui = gml.create("center", "center", 70, 30)
1705 ngui.style = gui.style
1706 ngui:addLabel("center", 1, 21, "Add new component")
1707 list = ngui:addListBox(2, 3, 64, 23, tab)
1708 list.onDoubleClick = addComponent
1709 ngui:addButton(36, 28, 14, 1, "Refresh", function()
1710 reloadList()
1711 list:updateList(tab)
1712 end)
1713 ngui:addButton(52, 28, 14, 1, "Close", function()
1714 selected = nil
1715 ngui:close()
1716 end)
1717 ngui:run()
1718
1719 return selected
1720end
1721
1722local function componentDetails(t)
1723 local shouldRefresh = false
1724 local dgui = gml.create("center", "center", 55, 16)
1725 dgui.style = gui.style
1726 dgui:addLabel("center", 1, 18, "Component details")
1727 dgui:addLabel(2, 3, 50, "UID: " .. t.id)
1728 local addrLabel = dgui:addLabel(2, 4, 50, "Address: " .. t.address)
1729 dgui:addLabel(2, 5, 48, "Type: " .. t.type)
1730 dgui:addLabel(2, 6, 7, "Name:")
1731 local name = dgui:addTextField(14, 6, 20)
1732 name.text = t.name or ""
1733 dgui:addLabel(2, 7, 9, "State:")
1734 local avail = dgui:addLabel(2, 8, 22, "")
1735 dgui:addLabel(2, 10, 17, "X coordinate:")
1736 dgui:addLabel(2, 11, 17, "Y coordinate:")
1737 dgui:addLabel(2, 12, 17, "Z coordinate:")
1738 local cx = dgui:addTextField(20, 10, 10)
1739 local cy = dgui:addTextField(20, 11, 10)
1740 local cz = dgui:addTextField(20, 12, 10)
1741 cx.text = t.x and tostring(t.x) or ""
1742 cy.text = t.y and tostring(t.y) or ""
1743 cz.text = t.z and tostring(t.z) or ""
1744
1745 addrLabel.onDoubleClick = function()
1746 local newAddr = bNewComponent(true, t.type)
1747 if newAddr then
1748 local proxy = component.proxy(newAddr)
1749 if proxy and proxy.type == t.type then
1750 addrLabel.newAddr = newAddr
1751 addrLabel.text = "Address: " .. newAddr
1752 addrLabel:draw()
1753 elseif proxy and proxy.type ~= type then
1754 GMLmessageBox(gui, "Component type cannot be changed.", {"OK"})
1755 else
1756 GMLmessageBox(gui, "Component isn't available.", {"OK"})
1757 end
1758 end
1759 end
1760
1761 local button = dgui:addButton(11, 7, 13, 1, t.state and "enabled" or "disabled", function(self)
1762 if self.status then
1763 self.text = "disabled"
1764 self.status = false
1765 self:draw()
1766 else
1767 self.text = "enabled"
1768 self.status = true
1769 self:draw()
1770 end
1771 end)
1772 button.status = t.state
1773 local function refreshAvail()
1774 if component.proxy(t.address) then
1775 avail.text = "Availability: online"
1776 else
1777 avail.text = "Availability: offline"
1778 button.status = false
1779 button.text = "disabled"
1780 button:draw()
1781 end
1782 avail:draw()
1783 end
1784 refreshAvail()
1785 dgui:addButton(25, 8, 12, 1, "Refresh", refreshAvail)
1786 dgui:addButton(4, 15, 14, 1, "Remove", function()
1787 if GMLmessageBox(gui, "Are you sure you want to remove this element?", {"Yes", "No"}) == "Yes" then
1788 for i, t2 in pairs(components) do
1789 if t.address == t2.address then
1790 components[i] = nil
1791 save.components(true)
1792 dgui:close()
1793 break
1794 end
1795 end
1796 end
1797 end)
1798 dgui:addButton(20, 15, 14, 1, "Save", function()
1799 local nx = tonumber(cx.text)
1800 local ny = tonumber(cy.text)
1801 local nz = tonumber(cz.text)
1802 if not nx and cx.text:len() > 0 then
1803 GMLmessageBox(gui, "The X coordinate is incorrect.", {"OK"})
1804 elseif not ny and cy.text:len() > 0 then
1805 GMLmessageBox(gui, "The Y coordinate is incorrect.", {"OK"})
1806 elseif not nz and cz.text:len() > 0 then
1807 GMLmessageBox(gui, "The Z coordinate is incorrect.", {"OK"})
1808 elseif name.text:len() > 20 then
1809 GMLmessageBox(gui, "Name cannot be longer than 20 characters.", {"OK"})
1810 else
1811 t.state = button.status
1812 t.name = name.text
1813 t.x = nx
1814 t.y = ny
1815 t.z = nz
1816 if addrLabel.newAddr then t.address = addrLabel.newAddr end
1817 save.components(true)
1818 computer.pushSignal("components_changed", t.type)
1819 dgui:close()
1820 shouldRefresh = true
1821 end
1822 end)
1823 dgui:addButton(36, 15, 14, 1, "Cancel", function() dgui:close() end)
1824 dgui:run()
1825 return shouldRefresh
1826end
1827
1828local function bComponentList()
1829 local list, tab = nil, nil
1830 local function refreshList()
1831 local buffer = {}
1832 for _, t in pairs(components) do
1833 if not buffer[t.type] then buffer[t.type] = {} end
1834 local str = "[" .. t.id .. "] "
1835 str = str .. t.name .. " "
1836 str = str .. "(" .. t.address:sub(1, 8) .. "...) "
1837 str = str .. (t.state and "ON" or "OFF")
1838 if t.x then str = str .. " X:" .. tostring(t.x) end
1839 if t.y then str = str .. " Y:" .. tostring(t.y) end
1840 if t.z then str = str .. " Z:" .. tostring(t.z) end
1841 if not component.proxy(t.address) then str = "*" .. str end
1842 table.insert(buffer[t.type], str)
1843 end
1844 local buffer2 = {}
1845 for a, b in pairs(buffer) do
1846 table.insert(buffer2, {a, b})
1847 end
1848 table.sort(buffer2, function(a, b) return string.byte(a[1], 1) < string.byte(b[1], 1) end)
1849 tab = {}
1850 for _, t in pairs(buffer2) do
1851 table.insert(tab, string.upper(t[1]) .. ":")
1852 for _, l in pairs(t[2]) do
1853 table.insert(tab, " " .. l)
1854 end
1855 end
1856 end
1857 local function enableAll()
1858 for _, t in pairs(components) do
1859 if component.proxy(t.address) then t.state = true end
1860 end
1861 refreshList()
1862 list:updateList(tab)
1863 save.components(true)
1864 computer.pushSignal("components_changed")
1865 end
1866 local function disableAll()
1867 for _, t in pairs(components) do
1868 t.state = false
1869 end
1870 refreshList()
1871 list:updateList(tab)
1872 save.component(true)
1873 computer.pushSignal("components_changed")
1874 end
1875 local function deleteOffline()
1876 if GMLmessageBox(gui, "Are you sure you want to remove all offline devices?", {"Yes", "No"}) == "Yes" then
1877 for i, t in pairs(components) do
1878 if not component.proxy(t.address) then components[i] = nil end
1879 end
1880 refreshList()
1881 list:updateList(tab)
1882 save.components(true)
1883 computer.pushSignal("components_changed")
1884 end
1885 end
1886 local function reloadList()
1887 refreshList()
1888 list:updateList(tab)
1889 end
1890 local function details()
1891 local first = list:getSelected():find("%[")
1892 local last = list:getSelected():find("%]")
1893 if first and last then
1894 local id = list:getSelected():sub(first + 1, last - 1)
1895 for _, t in pairs(components) do
1896 if t.id == id then
1897 local refresh = componentDetails(t)
1898 if refresh then reloadList() end
1899 break
1900 end
1901 end
1902 refreshList()
1903 list:updateList(tab)
1904 computer.pushSignal("components_changed")
1905 end
1906 end
1907
1908 refreshList()
1909 local cgui = gml.create("center", "center", 90, 30)
1910 cgui.style = gui.style
1911 cgui:addLabel("center", 1, 22, "Installed components")
1912 list = cgui:addListBox(2, 3, 84, 20, tab)
1913 list.onDoubleClick = details
1914 cgui:addLabel(4, 23, 32, "Legend: [ID] name (address...)")
1915 cgui:addLabel(68, 23, 13, "* - offline")
1916 cgui:addButton(3, 25, 21, 1, "Enable all", enableAll)
1917 cgui:addButton(3, 27, 21, 1, "Disable all", disableAll)
1918 cgui:addButton(26, 27, 18, 1, "Remove offline", deleteOffline)
1919 cgui:addButton(54, 27, 14, 1, "Refresh", reloadList)
1920 cgui:addButton(70, 27, 14, 1, "Close", function() cgui:close() end)
1921 cgui:run()
1922end
1923
1924local function bModuleList()
1925 local changeg = false
1926 local llist, rlist = nil, nil
1927 local ltab, rtab = {}, {}
1928
1929 local function refTabs()
1930 ltab = {}
1931 for i = 1, 4 do
1932 if modules[i] then
1933 if #ltab == 0 then table.insert(ltab, " NORMAL:") end
1934 table.insert(ltab, tostring(i) .. ". " .. modules[i].name .. ", " .. modules[i].version)
1935 end
1936 end
1937 if modules[5] then
1938 if #ltab > 0 then table.insert(ltab, "") end
1939 table.insert(ltab, " LANDSCAPE:")
1940 table.insert(ltab, "5. " .. modules[5].name .. ", " .. modules[5].version)
1941 end
1942
1943 rtab = {}
1944 local n, l = {}, {}
1945 for _, t in pairs(pmodules) do
1946 if t.shape == "normal" then
1947 table.insert(n, t.name .. ", " .. t.version)
1948 elseif t.shape == "landscape" then
1949 table.insert(l, t.name .. ", " .. t.version)
1950 end
1951 end
1952 if #n > 0 then
1953 table.insert(rtab, " NORMAL:")
1954 for _, e in pairs(n) do table.insert(rtab, e) end
1955 end
1956 if #l > 0 then
1957 if #n > 0 then table.insert(rtab, " LANDSCAPE:") end
1958 for _, e in pairs(l) do table.insert(rtab, e) end
1959 end
1960
1961 if #ltab == 0 then table.insert(ltab, "") end
1962 if #rtab == 0 then table.insert(rtab, "") end
1963 end
1964
1965 local function up()
1966 if #ltab > 1 then
1967 local num = tonumber(llist:getSelected():match("^(%d)%. .+"))
1968 if num and num > 1 and num < 5 then
1969 if modules[num - 1] then
1970 local buffer = modules[num - 1]
1971 modules[num - 1] = modules[num]
1972 modules[num] = buffer
1973 else
1974 modules[num - 1] = modules[num]
1975 modules[num] = nil
1976 end
1977 refTabs()
1978 llist:updateList(ltab)
1979 changed = true
1980 end
1981 end
1982 end
1983
1984 local function down()
1985 if #ltab > 1 then
1986 local num = tonumber(llist:getSelected():match("^(%d)%. .+"))
1987 if num and num > 0 and num < 4 then
1988 if modules[num + 1] then
1989 local buffer = modules[num + 1]
1990 modules[num + 1] = modules[num]
1991 modules[num] = buffer
1992 else
1993 modules[num + 1] = modules[num]
1994 modules[num] = nil
1995 end
1996 refTabs()
1997 llist:updateList(ltab)
1998 changed = true
1999 end
2000 end
2001 end
2002
2003 local function getIndex(pname)
2004 if not pname then return nil end
2005 for i, t in pairs(pmodules) do
2006 if t.name == pname then return i end
2007 end
2008 return nil
2009 end
2010
2011 local function toLeft()
2012 local index = getIndex(rlist:getSelected():match("^(.+), .+"))
2013 if index and pmodules[index].shape == "normal" then
2014 local free = nil
2015 for i = 1, 4 do
2016 if not modules[i] then free = i break end
2017 end
2018 if free then
2019 modules[free] = pmodules[index]
2020 pmodules[index] = nil
2021 refTabs()
2022 llist:updateList(ltab)
2023 rlist:updateList(rtab)
2024 changed = true
2025 else
2026 GMLmessageBox(gui, "No free zones.", {"OK"})
2027 end
2028 elseif index and pmodules[index].shape == "landscape" then
2029 if not modules[5] then
2030 modules[5] = pmodules[index]
2031 pmodules[index] = nil
2032 refTabs()
2033 llist:updateList(ltab)
2034 rlist:updateList(rtab)
2035 changed = true
2036 else
2037 GMLmessageBox(gui, "No free zones.", {"OK"})
2038 end
2039 end
2040 end
2041
2042 local function toRight()
2043 local num = tonumber(llist:getSelected():match("^(%d)%. .+"))
2044 if num then
2045 table.insert(pmodules, modules[num])
2046 modules[num] = nil
2047 refTabs()
2048 llist:updateList(ltab)
2049 rlist:updateList(rtab)
2050 changed = true
2051 end
2052 end
2053 refTabs()
2054 local mgui = gml.create("center", "center", 90, 24)
2055 mgui.style = gui.style
2056 mgui:addLabel("center", 1, 15, "Module list")
2057 mgui:addLabel(3, 3, 17, "Active modules:")
2058 mgui:addLabel(57, 3, 20, "Inactive modules:")
2059 llist = mgui:addListBox(3, 5, 30, 14, ltab)
2060 rlist = mgui:addListBox(57, 5, 30, 14, rtab)
2061 mgui:addButton(35, 8, 9, 1, "-->", toRight)
2062 mgui:addButton(46, 8, 9, 1, "<--", toLeft)
2063 mgui:addButton(35, 12, 6, 1, "/\\", up)
2064 mgui:addButton(35, 14, 6, 1, "\\/", down)
2065 mgui:addButton(71, 21, 14, 1, "Close", function() mgui:close() end)
2066
2067 mgui:run()
2068 if changed then
2069 save.modules(true)
2070 GMLmessageBox(gui, "Changes will be applied after server restart.", {"OK"})
2071 end
2072end
2073
2074local function componentDistribution()
2075 local list, all, tab = nil, nil
2076 local function refreshList()
2077 local buffer = {}
2078 local total = 0
2079 for _, c in pairs(component.list()) do
2080 if not buffer[c] then buffer[c] = 0 end
2081 buffer[c] = buffer[c] + 1
2082 total = total + 1
2083 end
2084 local b2 = {}
2085 for a, b in pairs(buffer) do
2086 table.insert(b2, {a, b})
2087 end
2088 table.sort(b2, function(a, b) return a[1]:byte(1) < b[1]:byte(1) end)
2089 tab = {}
2090 for _, t in pairs(b2) do
2091 table.insert(tab, t[1]:upper() .. ": " .. tostring(t[2]))
2092 end
2093 all.text = "Razem: " .. tostring(total)
2094 end
2095 local dgui = gml.create("center", "center", 50, 18)
2096 dgui.style = gui.style
2097 dgui:addLabel("center", 1, 22, "Component distrubution")
2098 all = dgui:addLabel(4, 13, 15, "")
2099 refreshList()
2100 list = dgui:addListBox(2, 3, 44, 9, tab)
2101 dgui:addButton(18, 15, 14, 1, "Refresh", function()
2102 refreshList()
2103 list:updateList(tab)
2104 all:draw()
2105 end)
2106 dgui:addButton(34, 15, 14, 1, "Close", function() dgui:close() end)
2107 dgui:run()
2108end
2109
2110local function bInformation()
2111 local igui = gml.create("center", "center", 50, 11)
2112 igui.style = gui.style
2113 igui:addLabel("center", 1, 11, "Information")
2114 igui:addLabel(2, 3, 14, "Disk usage:")
2115 igui:addLabel(2, 4, 16, "Memory usage:")
2116 igui:addLabel(2, 5, 23, "Connected component:")
2117 igui:addLabel(2, 6, 18, "Available energy:")
2118 local iHdd = igui:addLabel(26, 3, 20, "")
2119 local iMem = igui:addLabel(26, 4, 20, "")
2120 local iCom = igui:addLabel(26, 5, 20, "")
2121 local iEne = igui:addLabel(26, 6, 20, "")
2122 local function refreshInformation()
2123 local fs = component.proxy(computer.getBootAddress())
2124 if fs then
2125 local a = math.ceil(fs.spaceUsed() / 1024)
2126 local b = math.ceil(fs.spaceTotal() / 1024)
2127 local str = tostring(math.ceil(a / b * 100)) .. "% "
2128 str = str .. tostring(a) .. "/" .. tostring(b) .. "KB"
2129 iHdd.text = str
2130 else
2131 iHdd.text = "N/A"
2132 end
2133 iHdd:draw()
2134 local total = math.ceil(computer.totalMemory() / 1024)
2135 local free = total - math.ceil(computer.freeMemory() / 1024)
2136 local str = tostring(math.ceil(free / total * 100)) .. "% "
2137 str = str .. tostring(free) .. "/" .. tostring(total) .. "KB"
2138 iMem.text = str
2139 iMem:draw()
2140 local camount = 0
2141 for _, _ in component.list() do camount = camount + 1 end
2142 iCom.text = tostring(camount)
2143 iCom:draw()
2144 iEne.text = tostring(math.ceil(computer.energy() / computer.maxEnergy() * 100)) .. "%"
2145 iEne:draw()
2146 end
2147 iCom.onDoubleClick = componentDistribution
2148 refreshInformation()
2149 igui:addButton(18, 8, 14, 1, "Refresh", refreshInformation)
2150 igui:addButton(34, 8, 14, 1, "Close", function() igui:close() end)
2151 igui:run()
2152end
2153
2154local function backup(port)
2155 if GMLmessageBox(gui, "Are you sure you want to create a backup?", {"Yes", "No"}) == "No" then
2156 return
2157 end
2158 if not dsapi.echo(port or 1) then
2159 GMLmessageBox(gui, "Data server wasn't found.", {"OK"})
2160 return
2161 end
2162
2163 local list = {}
2164 local function updateList(path)
2165 local iter, err = fs.list(fs.concat("/etc", path))
2166 if not iter then
2167 GMLmessageBox(gui, "Couldn't create element list: " .. err, {"OK"})
2168 return false
2169 end
2170 for s in iter do
2171 local subpath = fs.concat(path, s)
2172 if s:sub(-1) == "/" then
2173 if not updateList(subpath) then return false end
2174 else
2175 table.insert(list, subpath)
2176 end
2177 end
2178 return true
2179 end
2180 local bgui
2181 local function beginBackup()
2182 local maxx = #list
2183 local success = true
2184 local errormsg = ""
2185 for _, t in pairs(list) do
2186 local file, e = io.open(fs.concat("/etc", t), "r")
2187 if file then
2188 local status, err = dsapi.write(port, t, file:read("*a"))
2189 file:close()
2190 if not status then
2191 errormsg = dsapi.translateCode(err)
2192 success = false
2193 break
2194 end
2195 else
2196 errormsg = e
2197 success = false
2198 break
2199 end
2200 end
2201 if success then
2202 GMLmessageBox(gui, "Backup has been successfully created!", {"OK"})
2203 else
2204 GMLmessageBox(gui, "Couldn't create a backup: " .. errormsg, {"OK"})
2205 end
2206 bgui:close()
2207 end
2208 if updateList("/the_guard") then
2209 bgui = gml.create("center", "center", 60, 7)
2210 bgui.style = gui.style
2211 bgui:addLabel("center", 2, 44, "During backup procedure server")
2212 bgui:addLabel("center", 3, 22, "may be unresponsive.")
2213 bgui:addButton("center", 5, 14, 1, "Start", beginBackup)
2214 bgui:run()
2215 end
2216end
2217
2218local function restore(port)
2219 if GMLmessageBox(gui, "Are you usre you want to restore data from backup?", {"Yes", "No"}) == "No" then
2220 return
2221 end
2222 if not dsapi.echo(port or 1) then
2223 GMLmessageBox(gui, "Data server wans't found.", {"OK"})
2224 return
2225 end
2226
2227 local function createList(list, path)
2228 local status, iter = dsapi.list(port, path)
2229 if status then
2230 for name, size in iter do
2231 local subpath = fs.concat(path, name)
2232 if size == -1 then
2233 local a, b = createList(list, subpath)
2234 if not a then return false, b end
2235 else
2236 table.insert(list, subpath)
2237 end
2238 end
2239 else
2240 return false, dsapi.translateCode(iter)
2241 end
2242 return true
2243 end
2244 local function checkDirectory(path)
2245 local segments = fs.segments(path)
2246 table.remove(segments, #segments)
2247 local subpath = ""
2248 for _, t in pairs(segments) do subpath = subpath .. "/" .. t end
2249 if not fs.isDirectory(subpath) then
2250 fs.makeDirectory(subpath)
2251 end
2252 end
2253 local rgui = nil
2254 local function beginRestore()
2255 local list = {}
2256 local status, e = createList(list, "/the_guard")
2257 local success = true
2258 if status then
2259 for _, t in pairs(list) do
2260 local s2, content = dsapi.get(port, t)
2261 if s2 then
2262 local subpath = fs.concat("/etc", t)
2263 checkDirectory(subpath)
2264 local file, e2 = io.open(subpath, "w")
2265 if file then
2266 file:write(content)
2267 file:close()
2268 else
2269 success = false
2270 GMLmessageBox(gui, "Couldn't create file: " .. e2, {"OK"})
2271 break
2272 end
2273 else
2274 success = false
2275 GMLmessageBox(gui, "Data restore operation failed: " .. dsapi.translateCode(content), {"OK"})
2276 break
2277 end
2278 end
2279 else
2280 success = false
2281 GMLmessageBox(gui, "Couldn't create file list: " .. e, {"OK"})
2282 end
2283 if success then
2284 GMLmessageBox(gui, "Data restore completed. The computer will reboot.", {"OK"})
2285 computer.shutdown(true)
2286 end
2287 rgui:close()
2288 end
2289
2290 rgui = gml.create("center", "center", 60, 8)
2291 rgui.style = gui.style
2292 rgui:addLabel("center", 2, 44, "During data restore server may be")
2293 rgui:addLabel("center", 3, 47, "unresponsive. After the operation")
2294 rgui:addLabel("center", 4, 30, "computer will reboot.")
2295 rgui:addButton("center", 6, 14, 1, "Start", beginRestore)
2296 rgui:run()
2297end
2298
2299local function bSettings()
2300 local sgui = gml.create("center", "center", 60, 13)
2301 sgui.style = gui.style
2302 sgui:addLabel("center", 1, 11, "Settings")
2303 sgui:addLabel(2, 3, 13, "Main port:")
2304 sgui:addLabel(2, 4, 12, "Backup port:")
2305 sgui:addLabel(2, 6, 18, "Debug mode:")
2306 sgui:addLabel(2, 7, 14, "Dark theme:")
2307 sgui:addLabel(2, 8, 23, "Save before closing:")
2308 local mainport = sgui:addTextField(16, 3, 9)
2309 mainport.text = tostring(settings.port)
2310 local backupport = sgui:addTextField(16, 4, 9)
2311 backupport.text = tostring(settings.backupPort)
2312 local function switchState(self)
2313 if self.status then
2314 self.status = false
2315 self.text = "no"
2316 else
2317 self.status = true
2318 self.text = "yes"
2319 end
2320 self:draw()
2321 end
2322 local function switchDebugMode(self)
2323 if not self.status then
2324 local mmsg =
2325[[
2326Activation of debug mode will turn
2327off password protection. Are you
2328sure you want to continue?
2329]]
2330 if GMLmessageBox(gui, mmsg, {"Yes", "No"}) == "No" then return end
2331 switchState(self)
2332 else
2333 switchState(self)
2334 end
2335 end
2336 local bDebug = sgui:addButton(26, 6, 11, 1, "", switchDebugMode)
2337 bDebug.text = settings.debugMode and "yes" or "no"
2338 bDebug.status = settings.debugMode
2339 local bDark = sgui:addButton(26, 7, 11, 1, "", switchState)
2340 bDark.text = settings.dark and "yes" or "no"
2341 bDark.status = settings.dark
2342 local bSave = sgui:addButton(26, 8, 11, 1, "", switchState)
2343 bSave.text = settings.saveOnExit and "yes" or "no"
2344 bSave.status = settings.saveOnExit
2345 sgui:addButton(41, 3, 16, 1, "Backup", function()
2346 local n = tonumber(backupport.text)
2347 if n and n > 1 and n < 65535 then
2348 backup(n)
2349 else
2350 backup(nil)
2351 end
2352 end)
2353 sgui:addButton(41, 5, 16, 1, "Restore", function()
2354 local n = tonumber(backupport.text)
2355 if n and n > 1 and n < 65535 then
2356 restore(n)
2357 else
2358 restore(nil)
2359 end
2360 end)
2361 sgui:addButton(27, 10, 14, 1, "Apply", function()
2362 local p1 = tonumber(mainport.text)
2363 local p2 = tonumber(backupport.text)
2364 if not p1 then
2365 GMLmessageBox(gui, "Main port is invalid.", {"OK"})
2366 elseif p1 > 65535 or p1 < 1 then
2367 GMLmessageBox(gui, "Main port is out of range.", {"OK"})
2368 elseif not p2 then
2369 GMLmessageBox(gui, "Backup port is invalid.", {"OK"})
2370 elseif p2 > 65535 or p2 < 1 then
2371 GMLmessageBox(gui, "Backup port is out of range.", {"OK"})
2372 else
2373 local p1open, p2open = false, false
2374 if modem and modem.isOpen(p1) then
2375 modem.close(p1)
2376 p1open = true
2377 end
2378 if modem and modem.isOpen(p2) then
2379 modem.close(p2)
2380 p2open = true
2381 end
2382 settings.port = p1
2383 settings.backupPort = p2
2384 settings.debugMode = bDebug.status
2385 settings.dark = bDark.status
2386 settings.saveOnExit = bSave.status
2387 if p1open and modem then modem.open(p1) end
2388 if p2open and modem then modem.open(p2) end
2389 save.settings(true)
2390 sgui:close()
2391 end
2392 end)
2393 sgui:addButton(43, 10, 14, 1, "Cancel", function() sgui:close() end)
2394 sgui:run()
2395end
2396
2397local function bLogs()
2398 local lineWidth = 85
2399 local ll, list = {}, nil
2400 local function refresh()
2401 ll = {}
2402 for _, s in pairs(lastlog) do
2403 table.insert(ll, s:sub(1, 82))
2404 end
2405 end
2406 local function details()
2407 local line = nil
2408 for _, s in pairs(lastlog) do
2409 if s:sub(1, 20) == list:getSelected():sub(1, 20) then
2410 line = s
2411 break
2412 end
2413 end
2414 if line then
2415 local buffer = {}
2416 local count = 0
2417
2418 for chunk in line:gmatch('[^\r\n]+') do
2419 local br = false
2420
2421 for i = 1, chunk:len(), lineWidth do
2422 count = count + 1
2423 local tmp = chunk:sub(i, i + lineWidth - 1)
2424 if i > 1 then
2425 tmp = ' ' .. tmp
2426 end
2427
2428 if count <= 10 then
2429 table.insert(buffer, tmp)
2430 else
2431 table.insert(buffer, '(...)')
2432 br = true
2433 break
2434 end
2435 end
2436
2437 if br then break end
2438 end
2439
2440 -- for i = 1, line:len(), lineWidth do
2441 -- count = count + 1
2442 -- if i < line:len() then
2443 -- if count < 10 then
2444 -- local tmp = line:sub(i, i + lineWidth)
2445 -- if count > 1 then
2446 -- tmp = " " .. tmp
2447 -- end
2448 -- table.insert(buffer, tmp)
2449 -- else
2450 -- table.insert(buffer, "(...)")
2451 -- break
2452 -- end
2453 -- else
2454 -- break
2455 -- end
2456 -- end
2457
2458 local dgui = gml.create("center", "center", lineWidth + 25, 6 + #buffer)
2459 dgui.style = gui.style
2460 dgui:addLabel("center", 1, 11, "Details")
2461 for i = 1, #buffer do
2462 dgui:addLabel(2, 2 + i, lineWidth + 5, buffer[i])
2463 end
2464 dgui:addButton(lineWidth + 7, 4 + #buffer, 14, 1, "Close", function() dgui:close() end)
2465 dgui:run()
2466 refresh()
2467 list:updateList(ll)
2468 end
2469 end
2470 refresh()
2471 local lgui = gml.create("center", "center", 90, 18)
2472 lgui.style = gui.style
2473 lgui:addLabel("center", 1, 14, "Latest logs")
2474 list = lgui:addListBox(3, 3, 84, 12, ll)
2475 list.onDoubleClick = details
2476 lgui:addButton(55, 17, 14, 1, "Refresh", function()
2477 refresh()
2478 list:updateList(ll)
2479 end)
2480 lgui:addButton(71, 17, 14, 1, "Close", function() lgui:close() end)
2481 lgui:run()
2482end
2483
2484local function bLock()
2485 if settings.debugMode then return end
2486 local lgui = gml.create(1, 1, resolution[1], resolution[2])
2487 lgui.style = gui.style
2488 lgui:addLabel("center", 23, 22, " << PROGRAM LOCKED >>")
2489 lgui:addButton("center", 25, 16, 3, "UNLOCK", function()
2490 if passwordPrompt() then
2491 lgui:close()
2492 else
2493 GMLmessageBox(lgui, "Entered password is incorrect.", {"OK"})
2494 end
2495 end)
2496 lgui:run()
2497end
2498
2499-- # Crash protection
2500local function safeCall(fun, ...)
2501 local s, r = pcall(fun, ...)
2502 if not s then
2503 GMLmessageBox(gui, "An error occurred during program execution.\nFurther details are available in logs.")
2504 silentLog("safeCall", r)
2505 gui:draw()
2506 return nil
2507 end
2508 return r
2509end
2510
2511local function secureErrorHandler(err)
2512 local trace = debug.traceback()
2513 trace = trace:gsub('\t', '')
2514 silentLog("secureFunction", err .. '\n' .. trace)
2515end
2516
2517local function secureFunction(fun, ...)
2518 local args = {...}
2519 local call = function()
2520 fun(table.unpack(args))
2521 end
2522 local s, r = xpcall(call, secureErrorHandler)
2523 if not s then
2524 GMLmessageBox(gui, "An error occurred during module execution.\nFurther details are available in logs.")
2525 gui:draw()
2526 return nil
2527 end
2528 return r
2529end
2530
2531local function errorHandler(err)
2532 internalLog("An error occurred", err, false, 0xff0000)
2533 io.stderr:write(debug.traceback())
2534 print()
2535end
2536
2537local function bExit(b)
2538 if not settings.debugMode then
2539 if not passwordPrompt() then
2540 GMLmessageBox(gui, "Entered password is incorrect.", {"OK"})
2541 return
2542 end
2543 end
2544 gui:close()
2545end
2546
2547-- # Main GUI
2548local function createMainGui()
2549 gui = gml.create(1, 1, resolution[1], resolution[2])
2550
2551 if settings.dark then
2552 local s, r = pcall(gml.loadStyle, "dark")
2553 if s then
2554 gui.style = r
2555 else
2556 internalLog("gui", "cannot load dark theme", false, 0xffff00)
2557 end
2558 end
2559
2560 addTitle(gui, 143, 3)
2561 gui:addLabel(150, 7, 8, "(" .. version .. ")")
2562 gui:addButton(141, 13, 16, 1, "Components", function() safeCall(bComponentList) end)
2563 gui:addButton(141, 15, 16, 1, "New component", function() safeCall(bNewComponent) end)
2564 gui:addButton(141, 17, 16, 1, "Modules", function() safeCall(bModuleList) end)
2565 gui:addButton(141, 19, 16, 1, "Information", function() safeCall(bInformation) end)
2566 gui:addButton(142, 25, 14, 1, "Settings", function() safeCall(bSettings) end)
2567 gui:addButton(142, 27, 14, 1, "Logs", function() safeCall(bLogs) end)
2568 gui:addButton(142, 29, 14, 1, "Lock program", function() safeCall(bLock) end)
2569 gui:addButton(142, 31, 14, 1, "Exit", function() safeCall(bExit) end)
2570
2571 addBar(gui, 138, 1, 39, false)
2572 addBar(gui, 1, 40, 158, true)
2573 addBar(gui, 69, 1, 19, false)
2574 addBar(gui, 69, 21, 19, false)
2575 addBar(gui, 1, 20, 68, true)
2576 addBar(gui, 70, 20, 68, true)
2577
2578 addSymbol(gui, 70, 1, 0x2566)
2579 addSymbol(gui, 139, 1, 0x2566)
2580 addSymbol(gui, 1, 21, 0x2560)
2581 addSymbol(gui, 1, 41, 0x2560)
2582 addSymbol(gui, 139, 21, 0x2563)
2583 addSymbol(gui, 160, 41, 0x2563)
2584 addSymbol(gui, 70, 41, 0x2569)
2585 addSymbol(gui, 139, 41, 0x2569)
2586 addSymbol(gui, 70, 21, 0x256c)
2587end
2588
2589-- # Loader
2590local function initializeActions()
2591 actions = {}
2592
2593 actions["the_guard"] = {
2594 [1] = {
2595 name = "reflect",
2596 type = "CORE",
2597 desc = "Invokes function of given component",
2598 p1type = "string",
2599 p2type = "string",
2600 p1desc = "Component UID",
2601 p2desc = "Function invocation",
2602 hidden = false,
2603 exec = function(p1, p2)
2604 local comp = interface.getComponent(internalMod, p1, false)
2605 if comp then
2606 local invocation = "component.proxy('" .. comp.address .. "')." .. p2
2607 local fun, err = load(invocation)
2608 if fun then
2609 fun()
2610 else
2611 silentLog("reflect", "function invocation failed: " .. err)
2612 end
2613 else
2614 silentLog("reflect", "couldn't find component with id " .. p1)
2615 end
2616 end
2617 }
2618 }
2619
2620 for _, t in pairs(modules) do
2621 local mul = mod[t.name].id * 100
2622 local buff = {}
2623 for n, at in pairs(mod[t.name].actions) do
2624 buff[n + mul] = at
2625 end
2626 actions[t.name] = buff
2627 end
2628end
2629
2630local function loadModules()
2631 local function doActionValidation(ac, name)
2632 local davn = "action validator"
2633 local counter = 0
2634 for i, t in pairs(ac) do
2635 if type(i) ~= "number" then
2636 internalLog(davn, "invalid identifier (" .. type(i) .. ")")
2637 return false
2638 elseif type(t["type"]) ~= "string" then
2639 internalLog(davn, "invalid type (" .. type(t["type"]) .. ")")
2640 return false
2641 elseif type(t["desc"]) ~= "string" then
2642 internalLog(davn, "invalid description (" .. type(t["desc"]) .. ")")
2643 return false
2644 elseif type(t["exec"]) ~= "function" then
2645 internalLog(davn, "no function definition")
2646 return false
2647 elseif t["hidden"] and type(t["hidden"]) ~= "boolean" then
2648 internalLog(davn, "visibility not defined")
2649 return false
2650 end
2651 local p1t = type(t["p1type"])
2652 if p1t == "string" then
2653 if not (t["p1type"] == "number" or t["p1type"] == "string" or t["p1type"] == "table" or t["p1type"] == "function" or t["p1type"] == "nil") then
2654 internalLog(davn, "1st parameter type is incorrect (" .. t["p1type"] .. ")")
2655 return false
2656 end
2657 if type(t["p1desc"]) ~= "string" then
2658 internalLog(davn, "1st parameter description is empty")
2659 return false
2660 end
2661 elseif p1t ~= "nil" then
2662 internalLog(davn, "1st parameter is invalid (" .. p1t .. ")")
2663 return false
2664 end
2665 local p2t = type(t["p2type"])
2666 if p2t == "string" then
2667 if not (t["p2type"] == "number" or t["p2type"] == "string" or t["p2type"] == "table" or t["p2type"] == "function" or t["p2type"] == "nil") then
2668 internalLog(davn, "2nd parameter type is incorrect (" .. t["p2type"] .. ")")
2669 return false
2670 end
2671 if type(t["p2desc"]) ~= "string" then
2672 internalLog(davn, "2nd parameter description is empty")
2673 return false
2674 end
2675 elseif p2t ~= "nil" then
2676 internalLog(davn, "2nd parameter is invalid (" .. p2t .. ")")
2677 return false
2678 end
2679 counter = counter + 1
2680 end
2681 internalLog(name .. ": registered " .. tostring(counter) .. " action(s)", "")
2682 return true
2683 end
2684
2685 local function checkID(id)
2686 for _, t in pairs(mod) do
2687 if t.id == id then return false end
2688 end
2689 return true
2690 end
2691
2692 local function doValidation(mo)
2693 local dvn = "validator"
2694 if type(mo.name) ~= "string" then
2695 internalLog(dvn, "missing name")
2696 return false
2697 end
2698 if type(mo.version) ~= "string" then
2699 internalLog(dvn, "missing version")
2700 return false
2701 elseif type(mo.id) ~= "number" then
2702 internalLog(dvn, "missing id")
2703 return false
2704 elseif not checkID(mo.id) then
2705 internalLog(dvn, "id " .. tostring(mo.id) .. " is already yesen")
2706 return false
2707 elseif type(mo.apiLevel) ~= "number" then
2708 internalLog(dvn, "missing api level")
2709 return false
2710 elseif mo.apiLevel < apiLevel then
2711 internalLog(dvn, "too old server version")
2712 return false
2713 elseif type(mo.shape) ~= "string" then
2714 internalLog(dvn, "no shape defined")
2715 return false
2716 elseif mo.shape ~= "normal" and mo.shape ~= "landscape" then
2717 internalLog(dvn, "invalid shape")
2718 return false
2719 elseif type(mo.setUI) ~= "function" then
2720 internalLog(dvn, "missing setUI() function")
2721 return false
2722 elseif type(mo.start) ~= "function" then
2723 internalLog(dvn, "missing start() function")
2724 return false
2725 elseif type(mo.stop) ~= "function" then
2726 internalLog(dvn, "missing stop() function")
2727 return false
2728 elseif type(mo.pullEvent) ~= "function" then
2729 internalLog(dvn, "missing pullEvent() function")
2730 return false
2731 elseif type(mo.actions) ~= "table" then
2732 internalLog(dvn, "missing action table")
2733 return false
2734 elseif not doActionValidation(mo.actions, mo.name) then
2735 return false
2736 end
2737 return true
2738 end
2739
2740 local function checkStrict()
2741 if strict then
2742 internalLog("loader", "strict mode is enabled - ending program")
2743 require("os").exit()
2744 end
2745 end
2746
2747 local function loadModule(filename)
2748 local name = filename:match("mod_tg_(.+)%.lua")
2749 internalLog(" " .. name, "", true, 0xffff00)
2750 local m, e = loadfile(filename)
2751 if m then
2752 local s, buffer = pcall(m)
2753 if s then
2754 if buffer and doValidation(buffer) then
2755 return buffer
2756 else
2757 internalLog("Module corrupted", "deactivating", true, 0xff0000)
2758 checkStrict()
2759 end
2760 else
2761 internalLog("Syntax error", buffer, true, 0xff0000)
2762 checkStrict()
2763 end
2764 else
2765 internalLog("loader", "loading error: " .. e)
2766 checkStrict()
2767 end
2768 return nil
2769 end
2770
2771 for num, tab in pairs(modules) do
2772 if type(num) ~= "number" then
2773 internalLog("loader", "invalid zone, removing")
2774 modules[num] = nil
2775 elseif num > 0 and num < 6 then
2776 if type(tab.file) == "string" then
2777 local buffer = loadModule(tab.file)
2778 if buffer then
2779 if not mod[buffer.name] then
2780 if (buffer.shape == "normal" and (num > 0 and num < 5)) or (buffer.shape == "landscape" and num == 5) then
2781 mod[buffer.name] = buffer
2782 modules[num].name = buffer.name
2783 modules[num].version = buffer.version
2784 modules[num].shape = buffer.shape
2785 modules[num].id = buffer.id
2786 else
2787 internaLog("loader", "zone not consistent with shape")
2788 modules[num] = nil
2789 end
2790 else
2791 internalLog("loader", "module with name '" .. buffer.name .. "' already exists", 0xffff00)
2792 end
2793 else
2794 modules[num] = nil
2795 table.insert(bmodules, tab.file)
2796 end
2797 else
2798 internalLog("loader", "entry is corrpted, removing")
2799 modules[num] = nil
2800 end
2801 end
2802 end
2803 initializeActions()
2804
2805 local function isAdded(filename)
2806 local v1, v2 = false, false
2807 for _, t in pairs(modules) do
2808 if t.file == filename then
2809 v1 = true
2810 break
2811 end
2812 end
2813 for _, t in pairs(bmodules) do
2814 if t == filename then
2815 v2 = true
2816 break
2817 end
2818 end
2819 return v1 or v2
2820 end
2821 internalLog("Loading inactive modules", "", true, 0x00a6ff)
2822 for n in fs.list(modulesDir) do
2823 local name = n:match("^mod_tg_(.+)%.lua$")
2824 if name and not isAdded(fs.concat(modulesDir, n)) then
2825 local buffer = loadModule(fs.concat(modulesDir, n))
2826 if buffer then
2827 local t = {
2828 name = buffer.name,
2829 file = fs.concat(modulesDir, n),
2830 version = buffer.version,
2831 shape = buffer.shape,
2832 id = buffer.id
2833 }
2834 table.insert(pmodules, t)
2835 end
2836 end
2837 end
2838end
2839
2840-- #Event listeners
2841backgroundListener = function(...)
2842 local params = {...}
2843 for m, t in pairs(events) do
2844 for _, n in pairs(t) do
2845 if params[1] == n and mod[m] then
2846 mod[m].pullEvent(...)
2847 end
2848 end
2849 end
2850end
2851
2852local function getComponentID(addr)
2853 for i, t in pairs(components) do
2854 if t.address == addr then return i end
2855 end
2856 return nil
2857end
2858
2859local function internalListener(...)
2860 local params = {...}
2861 if params[1] == "component_added" then
2862 local id = getComponentID(params[2])
2863 if id then
2864 components[id].state = true
2865 end
2866 elseif params[1] == "component_removed" then
2867 local id = getComponentID(params[2])
2868 if id then
2869 components[id].state = false
2870 end
2871 end
2872end
2873
2874local function createGUI()
2875 local function calcPosition(x, y, width, height, maxWidth, maxHeight)
2876 width = math.min(width, maxWidth)
2877 height = math.min(height, maxHeight)
2878
2879 if x == "left" then
2880 x = 1
2881 elseif x == "right" then
2882 x = maxWidth - width + 1
2883 elseif x == "center" then
2884 x = math.max(1, math.floor((maxWidth - width) / 2))
2885 elseif x < 0 then
2886 x = maxWidth - width + 2 + x
2887 elseif x < 1 then
2888 x = 1
2889 elseif x + width - 1 > maxWidth then
2890 x = maxWidth - width + 1
2891 end
2892
2893 if y == "top" then
2894 y = 1
2895 elseif y == "bottom" then
2896 y = maxHeight - height + 1
2897 elseif y == "center" then
2898 y = math.max(1, math.floor((maxHeight - height) / 2))
2899 elseif y < 0 then
2900 y = maxHeight - height + 2 + y
2901 elseif y < 1 then
2902 y = 1
2903 elseif y + height - 1 > maxHeight then
2904 y = maxHeight - height + 1
2905 end
2906
2907 return x, y, width, height
2908 end
2909 for z, t in pairs(modules) do
2910 if z > 0 and z < 6 then
2911 internalLog(t.name, "")
2912 local m = mod[t.name]
2913 if m then
2914 local coord = zones[z]
2915 local dim = m.shape == "normal" and zones.normal or zones.landscape
2916 local subgui = gml.create(coord[1], coord[2], dim[1] + 2, dim[2] + 2)
2917 local counter = 0
2918 subgui.addLabel = function(...)
2919 local aa = {...}
2920 local x, y, w = calcPosition(aa[2], aa[3], aa[4], 1, dim[1], dim[2])
2921 counter = counter + 1
2922 local g = gui:addLabel(x + coord[1] - 1, y + coord[2] - 1, w, aa[5])
2923 g.mark = true
2924 return g
2925 end
2926 subgui.addButton = function(...)
2927 local aa = {...}
2928 local x, y, w, h = calcPosition(aa[2], aa[3], aa[4], aa[5], dim[1], dim[2])
2929 counter = counter + 1
2930 local g = gui:addButton(x + coord[1] - 1, y + coord[2] - 1, w, h, aa[6], aa[7])
2931 g.mark = true
2932 return g
2933 end
2934 subgui.addTextField = function(...)
2935 local aa = {...}
2936 local x, y, w = calcPosition(aa[2], aa[3], aa[4], 1, dim[1], dim[2])
2937 counter = counter + 1
2938 local g = gui:addTextField(x + coord[1] - 1, y + coord[2] - 1, w, aa[5])
2939 g.mark = true
2940 return g
2941 end
2942 subgui.addListBox = function(...)
2943 local aa = {...}
2944 local x, y, w, h = calcPosition(aa[2], aa[3], aa[4], aa[5], dim[1], dim[2])
2945 counter = counter + 1
2946 local g = gui:addListBox(x + coord[1] - 1, y + coord[2] - 1, w, h, aa[6])
2947 g.mark = true
2948 return g
2949 end
2950 subgui.addComponent = function(obj, comp)
2951 comp.posX = comp.posX + coord[1] - 1
2952 comp.posY = comp.posY + coord[2] - 1
2953 comp.bodyX, comp.bodyY, comp.bodyW, comp.bodyH = GMLcalcBody(comp)
2954 gui:addComponent(comp)
2955 end
2956 local s, r = pcall(m.setUI, subgui)
2957
2958 if s then
2959 for _, t in pairs(gui.components) do
2960 if t.mark then
2961 if t.onClick then
2962 local o = t.onClick
2963 t.onClick = function(...)
2964 return secureFunction(o, ...)
2965 end
2966 end
2967 if t.onDoubleClick then
2968 local o = t.onDoubleClick
2969 t.onDoubleClick = function(...)
2970 return secureFunction(o, ...)
2971 end
2972 end
2973 if t.onBeginDrag then
2974 local o = t.onBeginDrag
2975 t.onBeginDrag = function(...)
2976 return secureFunction(o, ...)
2977 end
2978 end
2979 if t.onDrag then
2980 local o = t.onDrag
2981 t.onDrag = function(...)
2982 return secureFunction(o, ...)
2983 end
2984 end
2985 if t.onDrop then
2986 local o = t.onDrop
2987 t.onDrop = function(...)
2988 return secureFunction(o, ...)
2989 end
2990 end
2991 end
2992 end
2993 internalLog("added " .. counter .. " GUI element(s)", "", false)
2994 else
2995 internalLog("modGUI", "cannot open GUI: " .. r, false, 0xffff00)
2996 end
2997 else
2998 internalLog("modGUI", "integrity error, module not found", false, 0xff0000)
2999 end
3000 end
3001 end
3002end
3003
3004local function main()
3005 if fs.exists("/etc/the_guard") and not fs.isDirectory("/etc/the_guard") then
3006 internalLog("main", "deleting invalid directory")
3007 fs.remove("/etc/the_guard")
3008 end
3009 if not fs.exists("/etc/the_guard") then
3010 internalLog("main", "config directory missing, creating")
3011 fs.makeDirectory("/etc/the_guard")
3012 end
3013
3014 internalLog("Loading configuration", "", true, 0x00a6ff)
3015 if not loadConfig() then
3016 internalLog("main", "loading failed", false, 0xff0000)
3017 return false
3018 end
3019
3020 if not settings.debugMode then
3021 local try = 0
3022 repeat
3023 if passwordPrompt() then break end
3024 try = try + 1
3025 until try == 3
3026 if try == 3 then
3027 internalLog("main", "too many incorrect password attempts", false, 0xff0000)
3028 return false
3029 end
3030 end
3031
3032 internalLog("Loading modules", "", true, 0x00a6ff)
3033 loadModules()
3034
3035 internalLog("Launching modules", "", true, 0x00a6ff)
3036 for n, m in pairs(mod) do
3037 internalLog(n, "")
3038 local s, e = pcall(m.start, interface)
3039 if not s then
3040 internalLog("failure", e, false, 0xff0000)
3041 end
3042 end
3043
3044 internalLog("Creating GUI", "", true, 0x00a6ff)
3045 createMainGui()
3046
3047 internalLog("Creating modules' GUIs", "", true, 0x00a6ff)
3048 createGUI()
3049
3050 internalLog("init", "Loading event listeners")
3051 event.listen("component_added", internalListener)
3052 event.listen("component_removed", internalListener)
3053 for _, e in pairs(revents) do
3054 event.listen(e, backgroundListener)
3055 end
3056 eventsready = true
3057
3058 internalLog("Starting the server", "", false, 0x00ff00)
3059 os.sleep(0.5)
3060 gui:run()
3061 os.sleep(0.5)
3062 eventsready = nil
3063
3064 internalLog("init", "Unloading events listeners")
3065 event.ignore("component_added", internalListener)
3066 event.ignore("component_removed", internalListener)
3067 for _, e in pairs(revents) do
3068 event.ignore(e, backgroundListener)
3069 end
3070
3071 internalLog("Disabling modules", "", true, 0x00a6ff)
3072 for n, m in pairs(mod) do
3073 internalLog(n, "")
3074 xpcall(m.stop, errorHandler, interface)
3075 end
3076
3077 if settings.saveOnExit then
3078 internalLog("Saving configuration", "", true)
3079 saveConfig()
3080 end
3081 internalLog("Saving logs", "", true, 0x00a6ff)
3082 flushLog()
3083 return true
3084end
3085
3086local function loadToken()
3087 local path = fs.concat(configDir, "token")
3088 local ee = component.eeprom
3089 if not ee then
3090 internalLog("token", "EEPROM is missing!", false, 0xff0000)
3091 return false
3092 end
3093 if fs.isDirectory(path) then fs.remove(path) end
3094 if fs.exists(path) then
3095 local file, e = io.open(path, "r")
3096 if file then
3097 local dec = data.decode64(file:read("*a"))
3098 if dec then
3099 if ee.address == dec then
3100 token = data.md5(ee.address)
3101 return true
3102 else
3103 internalLog("token", "Tokens differ!", false, 0xff0000)
3104 end
3105 else
3106 internalLog("token", "Couldn't decode a token", false, 0xff0000)
3107 end
3108 else
3109 internalLog("token", "Couldn't open token file (" .. e .. ")", false, 0xff0000)
3110 end
3111 else
3112 internalLog("token", "Token is missing, creating a new one", false, 0xffff00)
3113 local file, e = io.open(path, "w")
3114 if file then
3115 file:write(data.encode64(ee.address))
3116 file:close()
3117 token = data.md5(ee.address)
3118 return true
3119 else
3120 internalLog("token", "Couldn't create a new token (" .. e .. ")", false, 0xff0000)
3121 end
3122 end
3123 return false
3124end
3125
3126local function init()
3127 if not fs.isDirectory(configDir) then
3128 fs.makeDirectory(configDir)
3129 end
3130 local prev = component.gpu.setForeground(0x00ff00)
3131 print("THE GUARD server, version " .. version)
3132 component.gpu.setForeground(prev)
3133 internalLog("init", "Loading token")
3134 if not loadToken() then
3135 internalLog("init", "Token initialization failed", false, 0xff0000)
3136 return
3137 end
3138 internalLog("init", "Initializing the server")
3139 if not main() then
3140 internalLog("init", "Initialization failed", false, 0xff0000)
3141 return
3142 end
3143end
3144
3145init()