Skip to content

Instantly share code, notes, and snippets.

@dimitriye98
Last active April 14, 2018 14:54
Show Gist options
  • Save dimitriye98/d3cb33a8cb3dd3939f29b8c305ca1b0d to your computer and use it in GitHub Desktop.
Save dimitriye98/d3cb33a8cb3dd3939f29b8c305ca1b0d to your computer and use it in GitHub Desktop.
local fs = require "filesystem"
local comp = require "computer"
local shell = require "shell"
local log = {}
local function _numberToBytes(n, acc)
local newAcc = string.char(n % 256)..acc
local nextN = math.floor(n / 256)
if nextN == 0 then
return newAcc
else
return _numberToBytes(math.floor(n / 256), string.char(n % 256)..acc)
end
end
local function numberToBytes(n, minBytes)
local ret = _numberToBytes(n, "")
if #ret < minBytes then
ret = string.rep("\0", minBytes - #ret)..ret
end
return ret
end
local function bytesToNumber(str, nAcc)
if str == "" then return nAcc end
return bytesToNumber(str:sub(2), (nAcc or 0) + str:byte())
end
local logMethods = {}
local logMt = {__index = logMethods}
function logMt:__len()
local nNewEntries = 0
if self.newEntries then
nNewEntries = #self.newEntries
end
return self.oldLogLength + nNewEntries
end
local function _resetFromFile(self, path)
if not path then path = self.path end
for i in pairs(self) do
self[i] = nil
end
local fileHandle = assert(io.open(path, "rb"))
assert(fileHandle:read(7) == "BLKLOG\n", "file is not log file")
self.path = path
self.readHandle = fileHandle
self.idToBlock = {}
self.blockToId = {}
self.highestIdUsed = -1
while true do
local idBytes = fileHandle:read(2)
if idBytes:sub(1, 1) == "\n" then break end
local id = bytesToNumber(idBytes)
local block = fileHandle:read("*l")
self.idToBlock[id] = block
self.blockToId[block] = id
if self.highestIdUsed < id then
self.highestIdUsed = id
end
end
self.logStart = fileHandle:seek("cur", -1)
self.oldLogLength = (fileHandle:seek("end") - self.logStart) / 2
end
function log.readFromFile(path)
local logHandle = setmetatable({}, logMt)
_resetFromFile(logHandle, path)
return logHandle
end
function log.create()
return setmetatable({
idToBlock = {};
blockToId = {};
}, logMt)
end
function logMethods:append(block)
if not self.newEntries then
self.newEntries = {}
end
if not self.blockToId[block] then
if not self.newBlockTypes then
self.newBlockTypes = {}
end
table.insert(self.newBlockTypes, block)
self.highestIdUsed = (self.highestIdUsed or -1) + 1
self.blockToId[block] = self.highestIdUsed
self.idToBlock[self.highestIdUsed] = block
end
table.insert(self.newEntries, block)
end
function logMethods:get(i)
if i > 0 then
if not self.readHandle or i > self.oldLogLength then
return self.newEntries and self.newEntries[i - self.oldLogLength] or nil
end
if self.lastBlockRead ~= i - 1 then
self.readHandle:seek("set", self.logStart + 2 * (i - 1))
end
else
if not self.newEntries then
if self.readHandle:seek("end", 2 * i) < self.logStart then return nil end
else
if not self.readHandle or -i <= #self.newEntries then
return self.newEntries[#self.newEntries + i + 1]
end
self.readHandle:seek("end", 2 * (i + #self.newEntries))
end
end
return self.idToBlock[bytesToNumber(self.readHandle:read(2))]
end
function _iter(self, i)
i = i + 1
local ret = self:get(i)
if ret then
return i, ret
end
end
function logMethods:iterate()
return _iter, self, 0
end
function logMethods:write(path)
if path == nil then path = self.path end
local tmpPath = "/tmp/tmpLogFile"..math.random(0, 999999)
while fs.exists(tmpPath) do
tmpPath = "/tmp/tempLogFile"..math.random(0, 999999)
end
local tmp, err = io.open(tmpPath, "wb")
assert(tmp, err)
tmp:write("BLKLOG\n")
for id, block in pairs(self.idToBlock) do
tmp:write(numberToBytes(id, 2))
tmp:write(block)
tmp:write("\n")
end
tmp:write("\n")
for n, block in self:iterate() do
tmp:write(numberToBytes(self.blockToId[block], 2))
end
tmp:close()
if path == self.path then
self.readHandle:close()
end
if fs.exists(path) then
fs.remove(path)
end
fs.rename(tmpPath, shell.resolve(path))
if path == self.path then
self.readHandle = io.open(self.path)
end
if not self.path then
self.path = path
end
self.newBlockTypes = nil
self.newEntries = nil
end
function logMethods:flush()
if not self.newEntries then return end
if self.newBlockTypes then
self:write()
else
self.readHandle:close()
local handle = io.open(self.path, "ab")
for i, v in ipairs(self.newEntries) do
handle:write(numberToBytes(self.blockToId[v], 2))
end
handle:close()
self.oldLogLength = self.oldLogLength + #self.newEntries
self.readHandle = io.open(self.path, "rb")
self.newEntries = nil
end
_resetFromFile(self)
end
return log
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment