Last active
April 14, 2018 14:54
-
-
Save dimitriye98/d3cb33a8cb3dd3939f29b8c305ca1b0d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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