Files
koreader/frontend/luadata.lua
NiLuJe 5c24470ea9 Logger: Use serpent instead of dump (#9588)
* Persist: support serpent, and use by default over dump (as we assume consistency > readability in Persist).
* Logger/Dbg: Use serpent instead of dump to dump tables (it's slightly more compact, honors __tostring, and will tag tables with their ref, which can come in handy when debugging).
* Dbg: Don't duplicate Logger's log function, just use it directly.
* Fontlist/ConfigDialog: Use serpent for the debug dump.
* Call `os.setlocale(C, "numeric")` on startup instead of peppering it around dump calls. It's process-wide, so it didn't make much sense.
* Trapper: Use LuaJIT's serde facilities instead of dump. They're more reliable in the face of funky input, much faster, and in this case, the data never makes it to human eyes, so a human-readable format didn't gain us anything.
2022-10-06 02:21:03 +02:00

189 lines
5.8 KiB
Lua

--[[--
Handles append-mostly data such as KOReader's bookmarks and dictionary search history.
]]
local LuaSettings = require("luasettings")
local dump = require("dump")
local lfs = require("libs/libkoreader-lfs")
local logger = require("logger")
local util = require("util")
local LuaData = LuaSettings:extend{
name = "",
max_backups = 9,
}
--- Creates a new LuaData instance.
function LuaData:open(file_path, name)
-- Backwards compat, just in case...
if type(name) == "table" then
name = name.name
end
-- NOTE: Beware, our new instance is new, but self is still LuaData!
local new = LuaData:extend{
name = name,
file = file_path,
data = {},
}
-- Some magic to allow for self-describing function names:
-- We'll use data_env both as the environment when loading the data, *and* its metatable,
-- *and* as the target of its index lookup metamethod.
-- Its NameEntry field is a function responsible for actually storing the data in the right place in the LuaData object.
-- It gets called via __index lookup in the global scope (i.e., the env) when Lua tries to resolve
-- the global NameEntry function calls in our stored data.
-- NOTE: We could also make the metatable's __index field point to a function, and handle the lookup ourselves inside it,
-- but using an empty env with loadfile is not a bad idea to begin with anyway ;).
local data_env = {}
data_env.__index = data_env
setmetatable(data_env, data_env)
data_env[new.name.."Entry"] = function(table)
if table.index then
-- We've got a deleted setting, overwrite with nil and be done with it.
if not table.data then
new.data[table.index] = nil
return
end
if type(table.data) == "table" then
new.data[table.index] = new.data[table.index] or {}
local size = util.tableSize(table.data)
if size == 1 then
-- It's an incremental array element, insert it in the array at its proper index
for key, value in pairs(table.data) do
new.data[table.index][key] = value
end
else
-- It's a complex table, just replace the whole thing
new.data[table.index] = table.data
end
else
new.data[table.index] = table.data
end
else
-- It's an untagged blob, use it as-is
new.data = table
end
end
local ok, err
if lfs.attributes(new.file, "mode") == "file" then
ok, err = loadfile(new.file, "t", data_env)
if ok then
logger.dbg("data is read from", new.file)
ok()
else
logger.dbg(new.file, "is invalid, removed.", err)
os.remove(new.file)
end
end
if not ok then
for i=1, new.max_backups, 1 do
local backup_file = new.file..".old."..i
if lfs.attributes(backup_file, "mode") == "file" then
ok, err = loadfile(backup_file, "t", data_env)
if ok then
logger.dbg("data is read from", backup_file)
ok()
break
else
logger.dbg(backup_file, "is invalid, removed.", err)
os.remove(backup_file)
end
end
end
end
return new
end
--- Saves a setting.
function LuaData:saveSetting(key, value)
self.data[key] = value
self:append{
index = key,
data = value,
}
return self
end
--- Deletes a setting.
function LuaData:delSetting(key)
self.data[key] = nil
self:append{
index = key,
}
return self
end
--- Adds item to table.
function LuaData:addTableItem(table_name, value)
local settings_table = self:has(table_name) and self:readSetting(table_name) or {}
table.insert(settings_table, value)
self.data[table_name] = settings_table
self:append{
index = table_name,
data = {[#settings_table] = value},
}
end
--- Removes index from table.
function LuaData:removeTableItem(key, index)
LuaSettings.removeTableItem(self, key, index)
self:flush()
return self
end
--- Appends settings to disk.
function LuaData:append(data)
if not self.file then return end
local f_out = io.open(self.file, "a")
if f_out ~= nil then
-- NOTE: This is a function call, with a table as its single argument. Parentheses are elided.
f_out:write(self.name.."Entry")
f_out:write(dump(data))
f_out:write("\n")
f_out:close()
end
return self
end
--- Replaces existing settings with table.
function LuaData:reset(table)
self.data = table
self:flush()
return self
end
--- Writes all settings to disk (does not append).
function LuaData:flush()
if not self.file then return end
if lfs.attributes(self.file, "mode") == "file" then
for i=1, self.max_backups, 1 do
if lfs.attributes(self.file..".old."..i, "mode") == "file" then
logger.dbg("LuaData: Rename", self.file .. ".old." .. i, "to", self.file .. ".old." .. i+1)
os.rename(self.file, self.file .. ".old." .. i+1)
else
break
end
end
logger.dbg("LuaData: Rename", self.file, "to", self.file .. ".old.1")
os.rename(self.file, self.file .. ".old.1")
end
logger.dbg("LuaData: Write to", self.file)
local f_out = io.open(self.file, "w")
if f_out ~= nil then
f_out:write("-- we can read Lua syntax here!\n")
f_out:write(self.name.."Entry")
f_out:write(dump(self.data))
f_out:write("\n")
f_out:close()
end
return self
end
return LuaData