mirror of
https://github.com/koreader/koreader
synced 2024-11-10 01:10:34 +00:00
04d9a557aa
Bump base for util.fsyncOpenedFile() and util.fsyncDirectory(). Use these to force flush to storage device when saving luasetting, docsetting, and history.lua. Also dont rotate them to .old until they are at least 60 seconds old. Also make auto_save_paging_count count right. Also bump crengine: open (as text) small or empty files
227 lines
5.8 KiB
Lua
227 lines
5.8 KiB
Lua
--[[--
|
|
This module handles generic settings as well as KOReader's global settings system.
|
|
]]
|
|
|
|
local dump = require("dump")
|
|
local ffiutil = require("ffi/util")
|
|
local lfs = require("libs/libkoreader-lfs")
|
|
local logger = require("logger")
|
|
|
|
local LuaSettings = {}
|
|
|
|
function LuaSettings:new(o)
|
|
o = o or {}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end
|
|
|
|
--- Opens a settings file.
|
|
function LuaSettings:open(file_path)
|
|
local new = {file=file_path}
|
|
local ok, stored
|
|
|
|
-- File being absent and returning an empty table is a use case,
|
|
-- so logger.warn() only if there was an existing file
|
|
local existing = lfs.attributes(new.file, "mode") == "file"
|
|
|
|
ok, stored = pcall(dofile, new.file)
|
|
if ok and stored then
|
|
new.data = stored
|
|
else
|
|
if existing then logger.warn("Failed reading", new.file, "(probably corrupted).") end
|
|
-- Fallback to .old if it exists
|
|
ok, stored = pcall(dofile, new.file..".old")
|
|
if ok and stored then
|
|
if existing then logger.warn("read from backup file", new.file..".old") end
|
|
new.data = stored
|
|
else
|
|
if existing then logger.warn("no usable backup file for", new.file, "to read from") end
|
|
new.data = {}
|
|
end
|
|
end
|
|
|
|
return setmetatable(new, {__index = LuaSettings})
|
|
end
|
|
|
|
--- @todo DocSettings can return a LuaSettings to use following awesome features.
|
|
function LuaSettings:wrap(data)
|
|
local new = {data = type(data) == "table" and data or {}}
|
|
return setmetatable(new, {__index = LuaSettings})
|
|
end
|
|
|
|
--[[--Reads child settings.
|
|
|
|
@usage
|
|
|
|
Settings:saveSetting("key", {
|
|
a = "b",
|
|
c = "true",
|
|
d = false,
|
|
})
|
|
|
|
local child = Settings:child("key")
|
|
|
|
child:readSetting("a")
|
|
-- result "b"
|
|
]]
|
|
function LuaSettings:child(key)
|
|
return LuaSettings:wrap(self:readSetting(key))
|
|
end
|
|
|
|
--- Reads a setting.
|
|
function LuaSettings:readSetting(key)
|
|
return self.data[key]
|
|
end
|
|
|
|
--- Saves a setting.
|
|
function LuaSettings:saveSetting(key, value)
|
|
self.data[key] = value
|
|
return self
|
|
end
|
|
|
|
--- Deletes a setting.
|
|
function LuaSettings:delSetting(key)
|
|
self.data[key] = nil
|
|
return self
|
|
end
|
|
|
|
--- Checks if setting exists.
|
|
function LuaSettings:has(key)
|
|
return self:readSetting(key) ~= nil
|
|
end
|
|
|
|
--- Checks if setting does not exist.
|
|
function LuaSettings:hasNot(key)
|
|
return self:readSetting(key) == nil
|
|
end
|
|
|
|
--- Checks if setting is `true`.
|
|
function LuaSettings:isTrue(key)
|
|
return string.lower(tostring(self:readSetting(key))) == "true"
|
|
end
|
|
|
|
--- Checks if setting is `false`.
|
|
function LuaSettings:isFalse(key)
|
|
return string.lower(tostring(self:readSetting(key))) == "false"
|
|
end
|
|
|
|
--- Checks if setting is `nil` or `true`.
|
|
function LuaSettings:nilOrTrue(key)
|
|
return self:hasNot(key) or self:isTrue(key)
|
|
end
|
|
|
|
--- Checks if setting is `nil` or `false`.
|
|
function LuaSettings:nilOrFalse(key)
|
|
return self:hasNot(key) or self:isFalse(key)
|
|
end
|
|
|
|
--- Flips `nil` or `true` to `false`.
|
|
function LuaSettings:flipNilOrTrue(key)
|
|
if self:nilOrTrue(key) then
|
|
self:saveSetting(key, false)
|
|
else
|
|
self:delSetting(key)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Flips `nil` or `false` to `true`.
|
|
function LuaSettings:flipNilOrFalse(key)
|
|
if self:nilOrFalse(key) then
|
|
self:saveSetting(key, true)
|
|
else
|
|
self:delSetting(key)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Flips setting to `true`.
|
|
function LuaSettings:flipTrue(key)
|
|
if self:isTrue(key) then
|
|
self:delSetting(key)
|
|
else
|
|
self:saveSetting(key, true)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Flips setting to `false`.
|
|
function LuaSettings:flipFalse(key)
|
|
if self:isFalse(key) then
|
|
self:delSetting(key)
|
|
else
|
|
self:saveSetting(key, false)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Adds item to table.
|
|
function LuaSettings:addTableItem(key, value)
|
|
local settings_table = self:has(key) and self:readSetting(key) or {}
|
|
table.insert(settings_table, value)
|
|
self:saveSetting(key, settings_table)
|
|
return self
|
|
end
|
|
|
|
--- Removes index from table.
|
|
function LuaSettings:removeTableItem(key, index)
|
|
local settings_table = self:has(key) and self:readSetting(key) or {}
|
|
table.remove(settings_table, index)
|
|
self:saveSetting(key, settings_table)
|
|
return self
|
|
end
|
|
|
|
--- Replaces existing settings with table.
|
|
function LuaSettings:reset(table)
|
|
self.data = table
|
|
return self
|
|
end
|
|
|
|
--- Writes settings to disk.
|
|
function LuaSettings:flush()
|
|
if not self.file then return end
|
|
local directory_updated = false
|
|
if lfs.attributes(self.file, "mode") == "file" then
|
|
-- As an additional safety measure (to the ffiutil.fsync* calls
|
|
-- used below), we only backup the file to .old when it has
|
|
-- not been modified in the last 60 seconds. This should ensure
|
|
-- in the case the fsync calls are not supported that the OS
|
|
-- may have itself sync'ed that file content in the meantime.
|
|
local mtime = lfs.attributes(self.file, "modification")
|
|
if mtime < os.time() - 60 then
|
|
os.rename(self.file, self.file .. ".old")
|
|
directory_updated = true -- fsync directory content too below
|
|
end
|
|
end
|
|
local f_out = io.open(self.file, "w")
|
|
if f_out ~= nil then
|
|
os.setlocale('C', 'numeric')
|
|
f_out:write("-- we can read Lua syntax here!\nreturn ")
|
|
f_out:write(dump(self.data))
|
|
f_out:write("\n")
|
|
ffiutil.fsyncOpenedFile(f_out) -- force flush to the storage device
|
|
f_out:close()
|
|
end
|
|
if directory_updated then
|
|
-- Ensure the file renaming is flushed to storage device
|
|
ffiutil.fsyncDirectory(self.file)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Closes settings file.
|
|
function LuaSettings:close()
|
|
self:flush()
|
|
end
|
|
|
|
--- Purges settings file.
|
|
function LuaSettings:purge()
|
|
if self.file then
|
|
os.remove(self.file)
|
|
end
|
|
return self
|
|
end
|
|
|
|
return LuaSettings
|