diff --git a/gfx/element.lua b/gfx/element.lua index eafaf6a..3504a98 100644 --- a/gfx/element.lua +++ b/gfx/element.lua @@ -1,4 +1,5 @@ local Event = require("gfx.event") +local Logger = require("logging").getGlobalLogger() local Element = { x = 1, @@ -31,6 +32,7 @@ function Element:new(o) end function Element:draw() + Logger:trace("draw", self) local dirty = self:_isDirty() if dirty then local win = self:_getWindow() @@ -51,6 +53,7 @@ function Element:getY() end function Element:setPos(x, y) + Logger:trace("setPos", x, y) if (x ~= nil and self.x ~= x) or (y ~= nil and self.y ~= y) then self.x = x or self.x self.y = y or self.y @@ -77,6 +80,7 @@ function Element:setY(y) end function Element:setParent(parent) + Logger:trace("setParent", self, parent) if self.parent ~= parent then self.parent = parent self:setDirty() diff --git a/gfx/prop/visibility.lua b/gfx/prop/visibility.lua deleted file mode 100644 index 7e0ab45..0000000 --- a/gfx/prop/visibility.lua +++ /dev/null @@ -1,38 +0,0 @@ -local Prop = require("gfx.prop") -local Visibility = Prop:new{ defaultState = true, uid = "VISIBILITY" } - -Visibility.VISIBLE = true -Visibility.INVISIBLE = not Visibility.VISIBLE - -function Visibility:with(elementType) - local propSelf = self - local defaultDraw = elementType.draw - local defaultHandleEvent = elementType.handleEvent - - function elementType:isVisible() - return propSelf:getState(self) - end - - function elementType:setVisible(visibility) - return propSelf:setState(self, visibility) - end - - function elementType:draw() - if self:isVisible() then - return defaultDraw(self) - end - return false - end - - -- Ignore events when invisible - function elementType:handleEvent(evt) - if self:isVisible() then - return defaultHandleEvent(self, evt) - end - return false - end - - return elementType -end - -return Visibility \ No newline at end of file diff --git a/itemcontroller.lua b/itemcontroller.lua index 2e5b937..dd73b57 100644 --- a/itemcontroller.lua +++ b/itemcontroller.lua @@ -1,6 +1,7 @@ local Chest = require("storage.chest") local ItemGroup = require("storage.itemgroup") local Storage = require("storage") +local Logging = require("logging") local Text = require("gfx.text") local List = require("gfx.list") local Event = require("gfx.event") @@ -8,13 +9,21 @@ local Padding = require("gfx.padding") local Container = require("gfx.container") local Element = require("gfx.element") local Progress = require("gfx.progress") -local Prop = require("gfx.prop") local Children = require("gfx.prop.children") local Orientation = require("gfx.prop.orientation") - local CACHE_FILE = "/.storage.cache" -LOCK_FILE = "/.storage.lock" +local LOCK_FILE = "/.storage.lock" +local LOG_FILE_PATH = "latest.log" +local LOG_FILE = fs.open(LOG_FILE_PATH, "w+") +local Logger = Logging.getGlobalLogger() + +Logger:setOutput(function(text) + LOG_FILE.write(text) + LOG_FILE.flush() + print(text) +end) +Logger:setLevel(Logging.LogLevel.TRACE) local function lock() if fs.exists(LOCK_FILE) then @@ -48,11 +57,12 @@ local function saveState(fname, ctrl) local file = fs.open(fname, "w+") file.write(textutils.serialize(ser)) file.close() + Logger:info("saved state to", fname) end local function loadState(fname, node) if not isLocked() then - print("Not locked! Loading cache...") + Logger:info("Not locked! Loading cache...") local file = fs.open(fname, "r") if file ~= nil then local ser = textutils.unserialize(file.readAll()) @@ -61,7 +71,7 @@ local function loadState(fname, node) end end - print("Controller must scan chests...") + Logger:info("Controller must scan chests...") local nodeName = peripheral.getName(node) local storageChests = {peripheral.find("inventory")} for i,v in ipairs(storageChests) do @@ -685,7 +695,7 @@ local PAGES = { bindTabActionButtons() keyboardButton:setOnClick(function() - print("Toggling keyboard...") + Logger:debug("Toggling keyboard...") pageState.showKeyboard = not pageState.showKeyboard reloadState() @@ -693,13 +703,13 @@ local PAGES = { end) importButton:setOnClick(function() - print("Importing...") + Logger:debug("Importing...") state.node:rescan() -- Safely handle transfers, priming computer for a full reset/rescan in case server stops mid-transaction state:itemTransaction(function() for _,nodeStack in ipairs(state.node:find(function(s) return not s:isEmpty() end)) do if not state.controller:insertStack(nodeStack) then - print("Couldn't find a free slot for: "..nodeStack:getSimpleName()) + Logger:warn("Couldn't find a free slot for: ", nodeStack:getSimpleName()) end end end) @@ -709,7 +719,7 @@ local PAGES = { end) sortButton:setOnClick(function() - print("Reorganizing...") + Logger:debug("Reorganizing...") pageState.sortMode = (pageState.sortMode % #SORT_MODE) + 1 reloadState() return true @@ -726,7 +736,7 @@ local PAGES = { local group = state:getExtra() if group == nil then - print("No group passed to GROUP_DETAIL") + Logger:error("No group passed to GROUP_DETAIL") state:setPage("MAIN") return NOOP end @@ -856,7 +866,7 @@ local PAGES = { REQUEST = function(state) local group = state:getExtra() if group == nil then - print("No group passed to REQUEST") + Logger:error("No group passed to REQUEST") state:setPage("MAIN") return NOOP end @@ -993,6 +1003,7 @@ local PAGES = { state.height, state.width, function() + Logger:debug("Exporting to access node") state.node:rescan() state:itemTransaction(function() group:transferTo(state.node, pageState.request) @@ -1024,6 +1035,7 @@ local PAGES = { -- Set up event management local function updateDisplayState() + Logger:debug("updateDisplayState") local dataRequestText = paddedRequestCount:findById("data_request") local dataDividerPad = paddedRequestCount:findById("data_divider") local dataAvailableText = paddedRequestCount:findById("data_available") @@ -1099,6 +1111,7 @@ function CONTROLLER_STATE:currentPageState(default, override) end function CONTROLLER_STATE:setPage(page, extra) + Logger:debug("new page", page, extra) self.nextPage = page self.extra = extra end diff --git a/logging.lua b/logging.lua new file mode 100644 index 0000000..44f8a07 --- /dev/null +++ b/logging.lua @@ -0,0 +1,204 @@ +local Logging = {} +Logging.__index = Logging + +local LogLevel = { + TRACE = 0, + DEBUG = 1, + INFO = 2, + WARNING = 3, + CRITICAL = 4, + ERROR = 5 +} + +for k,v in pairs(LogLevel) do + LogLevel[v] = k +end + +function LogLevel.isValid(value) + return LogLevel[LogLevel[value]] ~= nil +end + +function LogLevel.asNumber(value) + return type(value) == "number" and value or LogLevel[value] +end + +function LogLevel.isGreaterOrEqual(a, b) + return a >= b +end + +local Logger = {} + +function Logger:new(o) + local logger = {} + local output = print + if type(o) == "table" then + if type(o.level) == "number" and LogLevel.isValid(o.level) then + logger.level = o.level + end + if type(o.output) == "function" then + output = o.output + end + end + logger.output = output + setmetatable(logger, self) + logger.__index = logger + + return logger +end + +local RecursionSentinel = {} +function RecursionSentinel.make(table) + local obj = { table = table } + setmetatable(obj, RecursionSentinel) + return obj +end + +function RecursionSentinel.isSentinel(value) + return type(value) == "table" and getmetatable(value) == RecursionSentinel +end + +function RecursionSentinel.isKnown(knownTables, value) + return knownTables[value] ~= nil +end + +function RecursionSentinel.getSentinel(knownTables, value) + local sentinel = knownTables[value] + if sentinel == nil then + sentinel = knownTables[value] + knownTables[value] = RecursionSentinel.make(value) + end + return sentinel +end + +function RecursionSentinel.add(knownTables, value) + local sentinel = RecursionSentinel.make(value) + knownTables[value] = sentinel + return sentinel +end + +function RecursionSentinel.remove(knownTables, value) + knownTables[value] = nil +end + +local function cloneNonRecursive(value, sentinels) + if type(value) == "table" then + if RecursionSentinel.isKnown(sentinels, value) then + return RecursionSentinel.getSentinel(sentinels, value) + end + + local clone = {} + local sentinel = RecursionSentinel.add(sentinels, value) + for i,v in ipairs(value) do + clone[i] = cloneNonRecursive(v, sentinels) + end + for k,v in pairs(value) do + clone[cloneNonRecursive(k, sentinels)] = cloneNonRecursive(v, sentinels) + end + RecursionSentinel.remove(sentinels, value) + + sentinel.value = clone + return sentinel.value + else + return value + end +end + +local function _simpleStringify(value, builder) + if type(value) == "table" then + table.insert(builder, "<") + if RecursionSentinel.isSentinel(value) then + table.insert(builder, "recurse ") + table.insert(builder, tostring(value.value)) + else + table.insert(builder, tostring(value)) + table.insert(builder, ">{") + local first = true + for i,v in ipairs(value) do + if not first then + table.insert(builder, ",") + else + first = false + end + _simpleStringify(v, builder) + end + first = #value == 0 + for k,v in pairs(value) do + if not first then + table.insert(builder, ",") + else + first = false + end + _simpleStringify(k, builder) + table.insert(builder, "=") + _simpleStringify(v, builder) + end + table.insert(builder, "}") + end + table.insert(builder, ">") + else + local isString = type(value) == "string" + if isString then + table.insert(builder, "\"") + end + table.insert(builder, tostring(value)) + if isString then + table.insert(builder, "\"") + end + end +end + +function Logger:_doPrint(level, message, ...) + if LogLevel.isGreaterOrEqual(level, self.level) then + local result = { tostring(LogLevel[level]), message } + for _,v in ipairs({...}) do + _simpleStringify(cloneNonRecursive(v), result) + end + self.output(table.concat(result, " ")) + end +end + +function Logger:trace(message, ...) + self:_doPrint(LogLevel.TRACE, message, ...) +end + +function Logger:debug(message, ...) + self:_doPrint(LogLevel.DEBUG, message, ...) +end + +function Logger:info(message, ...) + self:_doPrint(LogLevel.INFO, message, ...) +end + +function Logger:warn(message, ...) + self:_doPrint(LogLevel.WARNING, message, ...) +end + +function Logger:critical(message, ...) + self:_doPrint(LogLevel.CRITICAL, message, ...) +end + +function Logger:error(message, ...) + self:_doPrint(LogLevel.ERROR, message, ...) +end + +function Logger:setOutput(output) + self.output = output +end + +function Logger:setLevel(level) + if LogLevel.isValid(level) then + self.level = LogLevel.asNumber(level) + end +end + +Logging.Logger = Logger +Logging.LogLevel = LogLevel + +function Logging.getGlobalLogger() + if _GLOBAL_LOGGER == nil then + _GLOBAL_LOGGER = Logger:new{ level = LogLevel.DEBUG, output = print } + end + return _GLOBAL_LOGGER +end + +return Logging \ No newline at end of file