2
0
mirror of https://github.com/koreader/koreader synced 2024-10-31 21:20:20 +00:00
koreader/frontend/readhistory.lua
poire-z 04d9a557aa Use fsync() for more robust setting files saving
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
2019-12-10 23:57:32 +01:00

228 lines
6.6 KiB
Lua

local DataStorage = require("datastorage")
local DocSettings = require("docsettings")
local dump = require("dump")
local ffiutil = require("ffi/util")
local getFriendlySize = require("util").getFriendlySize
local joinPath = ffiutil.joinPath
local lfs = require("libs/libkoreader-lfs")
local realpath = ffiutil.realpath
local history_file = joinPath(DataStorage:getDataDir(), "history.lua")
local ReadHistory = {
hist = {},
last_read_time = 0,
}
local function buildEntry(input_time, input_file)
local file_exists = lfs.attributes(input_file, "mode") == "file"
return {
time = input_time,
text = input_file:gsub(".*/", ""),
file = realpath(input_file) or input_file, -- keep orig file path of deleted files
dim = not file_exists, -- "dim", as expected by Menu
mandatory = file_exists and getFriendlySize(lfs.attributes(input_file, "size") or 0),
callback = function()
local ReaderUI = require("apps/reader/readerui")
ReaderUI:showReader(input_file)
end
}
end
local function fileFirstOrdering(l, r)
if l.file == r.file then
return l.time > r.time
else
return l.file < r.file
end
end
local function timeFirstOrdering(l, r)
if l.time == r.time then
return l.file < r.file
else
return l.time > r.time
end
end
function ReadHistory:_indexing(start)
assert(self ~= nil)
--- @todo (Hzj_jie): Use binary search to find an item when deleting it.
for i = start, #self.hist, 1 do
self.hist[i].index = i
end
end
function ReadHistory:_sort()
assert(self ~= nil)
local autoremove_deleted_items_from_history =
not G_reader_settings:nilOrFalse("autoremove_deleted_items_from_history")
if autoremove_deleted_items_from_history then
self:clearMissing()
end
table.sort(self.hist, fileFirstOrdering)
--- @todo (zijiehe): Use binary insert instead of a loop to deduplicate.
for i = #self.hist, 2, -1 do
if self.hist[i].file == self.hist[i - 1].file then
table.remove(self.hist, i)
end
end
table.sort(self.hist, timeFirstOrdering)
self:_indexing(1)
end
-- Reduces total count in hist list to a reasonable number by removing last
-- several items.
function ReadHistory:_reduce()
assert(self ~= nil)
while #self.hist > 500 do
table.remove(self.hist, #self.hist)
end
end
-- Flushes current history table into file.
function ReadHistory:_flush()
assert(self ~= nil)
local content = {}
for k, v in pairs(self.hist) do
content[k] = {
time = v.time,
file = v.file
}
end
local f = io.open(history_file, "w")
f:write("return " .. dump(content) .. "\n")
ffiutil.fsyncOpenedFile(f) -- force flush to the storage device
f:close()
end
--- Reads history table from file.
-- @treturn boolean true if the history_file has been updated and reloaded.
function ReadHistory:_read()
assert(self ~= nil)
local history_file_modification_time = lfs.attributes(history_file, "modification")
if history_file_modification_time == nil
or history_file_modification_time <= self.last_read_time then
return false
end
self.last_read_time = history_file_modification_time
local ok, data = pcall(dofile, history_file)
if ok and data then
for k, v in pairs(data) do
table.insert(self.hist, buildEntry(v.time, v.file))
end
end
return true
end
-- Reads history from legacy history folder
function ReadHistory:_readLegacyHistory()
assert(self ~= nil)
local history_dir = DataStorage:getHistoryDir()
for f in lfs.dir(history_dir) do
local path = joinPath(history_dir, f)
if lfs.attributes(path, "mode") == "file" then
path = DocSettings:getPathFromHistory(f)
if path ~= nil and path ~= "" then
local file = DocSettings:getNameFromHistory(f)
if file ~= nil and file ~= "" then
table.insert(
self.hist,
buildEntry(lfs.attributes(joinPath(history_dir, f), "modification"),
joinPath(path, file)))
end
end
end
end
end
function ReadHistory:_init()
assert(self ~= nil)
self:reload()
end
function ReadHistory:clearMissing()
assert(self ~= nil)
for i = #self.hist, 1, -1 do
if self.hist[i].file == nil or lfs.attributes(self.hist[i].file, "mode") ~= "file" then
table.remove(self.hist, i)
end
end
end
function ReadHistory:removeItemByPath(path)
assert(self ~= nil)
for i = #self.hist, 1, -1 do
if self.hist[i].file == path then
self:removeItem(self.hist[i])
break
end
end
end
function ReadHistory:updateItemByPath(old_path, new_path)
assert(self ~= nil)
for i = #self.hist, 1, -1 do
if self.hist[i].file == old_path then
self.hist[i].file = new_path
self:_flush()
self.hist[i].callback = function()
local ReaderUI = require("apps/reader/readerui")
ReaderUI:showReader(new_path)
end
break
end
end
end
function ReadHistory:removeItem(item)
assert(self ~= nil)
table.remove(self.hist, item.index)
os.remove(DocSettings:getHistoryPath(item.file))
self:_indexing(item.index)
self:_flush()
end
function ReadHistory:addItem(file)
assert(self ~= nil)
if file ~= nil and lfs.attributes(file, "mode") == "file" then
local now = os.time()
table.insert(self.hist, 1, buildEntry(now, file))
--- @todo (zijiehe): We do not need to sort if we can use binary insert and
-- binary search.
-- util.execute("/bin/touch", "-a", file)
-- This emulates `touch -a` in LuaFileSystem's API, since it may be absent (Android)
-- or provided by busybox, which doesn't support the `-a` flag.
local mtime = lfs.attributes(file, "modification")
lfs.touch(file, now, mtime)
self:_sort()
self:_reduce()
self:_flush()
end
end
function ReadHistory:setDeleted(item)
assert(self ~= nil)
if self.hist[item.index] then
self.hist[item.index].dim = true
end
end
--- Reloads history from history_file.
-- @treturn boolean true if history_file has been updated and reload happened.
function ReadHistory:reload()
assert(self ~= nil)
if self:_read() then
self:_readLegacyHistory()
self:_sort()
self:_reduce()
return true
end
return false
end
ReadHistory:_init()
return ReadHistory