mirror of
https://github.com/koreader/koreader.git
synced 2025-12-18 12:02:09 +01:00
ReadHistory: support reload (#3003)
Allows ReadHistory to reload according to the modification timestamp of history.lua.
This commit is contained in:
@@ -1,14 +1,15 @@
|
|||||||
local lfs = require("libs/libkoreader-lfs")
|
|
||||||
local DataStorage = require("datastorage")
|
local DataStorage = require("datastorage")
|
||||||
local DocSettings = require("docsettings")
|
local DocSettings = require("docsettings")
|
||||||
local realpath = require("ffi/util").realpath
|
|
||||||
local joinPath = require("ffi/util").joinPath
|
|
||||||
local dump = require("dump")
|
local dump = require("dump")
|
||||||
|
local joinPath = require("ffi/util").joinPath
|
||||||
|
local lfs = require("libs/libkoreader-lfs")
|
||||||
|
local realpath = require("ffi/util").realpath
|
||||||
|
|
||||||
local history_file = joinPath(DataStorage:getDataDir(), "history.lua")
|
local history_file = joinPath(DataStorage:getDataDir(), "history.lua")
|
||||||
|
|
||||||
local ReadHistory = {
|
local ReadHistory = {
|
||||||
hist = {},
|
hist = {},
|
||||||
|
last_read_time = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
local function buildEntry(input_time, input_file)
|
local function buildEntry(input_time, input_file)
|
||||||
@@ -40,6 +41,7 @@ local function timeFirstOrdering(l, r)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ReadHistory:_indexing(start)
|
function ReadHistory:_indexing(start)
|
||||||
|
assert(self ~= nil)
|
||||||
-- TODO(Hzj_jie): Use binary search to find an item when deleting it.
|
-- TODO(Hzj_jie): Use binary search to find an item when deleting it.
|
||||||
for i = start, #self.hist, 1 do
|
for i = start, #self.hist, 1 do
|
||||||
self.hist[i].index = i
|
self.hist[i].index = i
|
||||||
@@ -47,7 +49,9 @@ function ReadHistory:_indexing(start)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ReadHistory:_sort()
|
function ReadHistory:_sort()
|
||||||
local autoremove_deleted_items_from_history = G_reader_settings:readSetting("autoremove_deleted_items_from_history") or false
|
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
|
if autoremove_deleted_items_from_history then
|
||||||
self:clearMissing()
|
self:clearMissing()
|
||||||
end
|
end
|
||||||
@@ -70,6 +74,7 @@ end
|
|||||||
-- Reduces total count in hist list to a reasonable number by removing last
|
-- Reduces total count in hist list to a reasonable number by removing last
|
||||||
-- several items.
|
-- several items.
|
||||||
function ReadHistory:_reduce()
|
function ReadHistory:_reduce()
|
||||||
|
assert(self ~= nil)
|
||||||
while #self.hist > 500 do
|
while #self.hist > 500 do
|
||||||
table.remove(self.hist, #self.hist)
|
table.remove(self.hist, #self.hist)
|
||||||
end
|
end
|
||||||
@@ -77,6 +82,7 @@ end
|
|||||||
|
|
||||||
-- Flushes current history table into file.
|
-- Flushes current history table into file.
|
||||||
function ReadHistory:_flush()
|
function ReadHistory:_flush()
|
||||||
|
assert(self ~= nil)
|
||||||
local content = {}
|
local content = {}
|
||||||
for k, v in pairs(self.hist) do
|
for k, v in pairs(self.hist) do
|
||||||
content[k] = {
|
content[k] = {
|
||||||
@@ -89,18 +95,28 @@ function ReadHistory:_flush()
|
|||||||
f:close()
|
f:close()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Reads history table from file
|
--- Reads history table from file.
|
||||||
|
-- @treturn boolean true if the history_file has been updated and reloaded.
|
||||||
function ReadHistory:_read()
|
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)
|
local ok, data = pcall(dofile, history_file)
|
||||||
if ok and data then
|
if ok and data then
|
||||||
for k, v in pairs(data) do
|
for k, v in pairs(data) do
|
||||||
table.insert(self.hist, buildEntry(v.time, v.file))
|
table.insert(self.hist, buildEntry(v.time, v.file))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Reads history from legacy history folder
|
-- Reads history from legacy history folder
|
||||||
function ReadHistory:_readLegacyHistory()
|
function ReadHistory:_readLegacyHistory()
|
||||||
|
assert(self ~= nil)
|
||||||
local history_dir = DataStorage:getHistoryDir()
|
local history_dir = DataStorage:getHistoryDir()
|
||||||
for f in lfs.dir(history_dir) do
|
for f in lfs.dir(history_dir) do
|
||||||
local path = joinPath(history_dir, f)
|
local path = joinPath(history_dir, f)
|
||||||
@@ -120,13 +136,12 @@ function ReadHistory:_readLegacyHistory()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ReadHistory:_init()
|
function ReadHistory:_init()
|
||||||
self:_read()
|
assert(self ~= nil)
|
||||||
self:_readLegacyHistory()
|
self:reload()
|
||||||
self:_sort()
|
|
||||||
self:_reduce()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function ReadHistory:clearMissing()
|
function ReadHistory:clearMissing()
|
||||||
|
assert(self ~= nil)
|
||||||
for i = #self.hist, 1, -1 do
|
for i = #self.hist, 1, -1 do
|
||||||
if self.hist[i].file == nil or lfs.attributes(self.hist[i].file, "mode") ~= "file" then
|
if self.hist[i].file == nil or lfs.attributes(self.hist[i].file, "mode") ~= "file" then
|
||||||
table.remove(self.hist, i)
|
table.remove(self.hist, i)
|
||||||
@@ -135,6 +150,7 @@ function ReadHistory:clearMissing()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ReadHistory:removeItemByPath(path)
|
function ReadHistory:removeItemByPath(path)
|
||||||
|
assert(self ~= nil)
|
||||||
for i = #self.hist, 1, -1 do
|
for i = #self.hist, 1, -1 do
|
||||||
if self.hist[i].file == path then
|
if self.hist[i].file == path then
|
||||||
self:removeItem(self.hist[i])
|
self:removeItem(self.hist[i])
|
||||||
@@ -144,6 +160,7 @@ function ReadHistory:removeItemByPath(path)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ReadHistory:removeItem(item)
|
function ReadHistory:removeItem(item)
|
||||||
|
assert(self ~= nil)
|
||||||
table.remove(self.hist, item.index)
|
table.remove(self.hist, item.index)
|
||||||
os.remove(DocSettings:getHistoryPath(item.file))
|
os.remove(DocSettings:getHistoryPath(item.file))
|
||||||
self:_indexing(item.index)
|
self:_indexing(item.index)
|
||||||
@@ -151,6 +168,7 @@ function ReadHistory:removeItem(item)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ReadHistory:addItem(file)
|
function ReadHistory:addItem(file)
|
||||||
|
assert(self ~= nil)
|
||||||
if file ~= nil and lfs.attributes(file, "mode") == "file" then
|
if file ~= nil and lfs.attributes(file, "mode") == "file" then
|
||||||
table.insert(self.hist, 1, buildEntry(os.time(), file))
|
table.insert(self.hist, 1, buildEntry(os.time(), file))
|
||||||
-- TODO(zijiehe): We do not need to sort if we can use binary insert and
|
-- TODO(zijiehe): We do not need to sort if we can use binary insert and
|
||||||
@@ -161,6 +179,20 @@ function ReadHistory:addItem(file)
|
|||||||
end
|
end
|
||||||
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()
|
ReadHistory:_init()
|
||||||
|
|
||||||
return ReadHistory
|
return ReadHistory
|
||||||
|
|||||||
@@ -1,51 +1,60 @@
|
|||||||
describe("ReadHistory module", function()
|
describe("ReadHistory module", function()
|
||||||
local DocSettings
|
local DocSettings
|
||||||
local DataStorage
|
local DataStorage
|
||||||
|
local joinPath
|
||||||
local mkdir
|
local mkdir
|
||||||
local realpath
|
local realpath
|
||||||
local joinPath
|
local reload
|
||||||
local usleep
|
local usleep
|
||||||
local history_file
|
|
||||||
|
|
||||||
local function file(name)
|
local function file(name)
|
||||||
return joinPath(DataStorage:getDataDir(), name)
|
return joinPath(DataStorage:getDataDir(), name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function test_data_dir()
|
||||||
|
return joinPath(DataStorage:getDataDir(), "testdata")
|
||||||
|
end
|
||||||
|
|
||||||
local function test_file(name)
|
local function test_file(name)
|
||||||
return joinPath(joinPath(DataStorage:getDataDir(), "testdata"), name)
|
return joinPath(test_data_dir(), name)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function legacy_history_file(name)
|
local function legacy_history_file(name)
|
||||||
return DocSettings:getHistoryPath(realpath(test_file(name)))
|
return DocSettings:getHistoryPath(realpath(test_file(name)))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function reload()
|
|
||||||
package.loaded["readhistory"] = nil
|
|
||||||
return require("readhistory")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function rm(file)
|
local function rm(file)
|
||||||
os.remove(file)
|
os.remove(file)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function mv(source, target)
|
||||||
|
os.rename(source, target)
|
||||||
|
end
|
||||||
|
|
||||||
local function touch(file)
|
local function touch(file)
|
||||||
local f = io.open(file, "w")
|
local f = io.open(file, "w")
|
||||||
f:close()
|
f:close()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function assert_item_is(h, i, name)
|
local function assert_item_is(h, i, name, fileRemoved)
|
||||||
assert.is.same(h.hist[i].text, name)
|
assert.is.same(h.hist[i].text, name)
|
||||||
assert.is.same(h.hist[i].index, i)
|
assert.is.same(h.hist[i].index, i)
|
||||||
assert.is.same(h.hist[i].file, realpath(test_file(name)))
|
assert.is.same(h.hist[i].file, joinPath(realpath(test_data_dir()), name))
|
||||||
|
if fileRemoved then
|
||||||
|
assert.is_nil(realpath(test_file(name)))
|
||||||
|
else
|
||||||
|
assert.is.same(h.hist[i].file, realpath(test_file(name)))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
setup(function()
|
setup(function()
|
||||||
require("commonrequire")
|
require("commonrequire")
|
||||||
DocSettings = require("docsettings")
|
DocSettings = require("docsettings")
|
||||||
DataStorage = require("datastorage")
|
DataStorage = require("datastorage")
|
||||||
|
joinPath = require("ffi/util").joinPath
|
||||||
mkdir = require("libs/libkoreader-lfs").mkdir
|
mkdir = require("libs/libkoreader-lfs").mkdir
|
||||||
realpath = require("ffi/util").realpath
|
realpath = require("ffi/util").realpath
|
||||||
joinPath = require("ffi/util").joinPath
|
reload = function() return package.reload("readhistory") end
|
||||||
usleep = require("ffi/util").usleep
|
usleep = require("ffi/util").usleep
|
||||||
|
|
||||||
mkdir(joinPath(DataStorage:getDataDir(), "testdata"))
|
mkdir(joinPath(DataStorage:getDataDir(), "testdata"))
|
||||||
@@ -277,4 +286,84 @@ describe("ReadHistory module", function()
|
|||||||
rm(to_file(i))
|
rm(to_file(i))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("should reload the history file if it updated", function()
|
||||||
|
-- Prepare a history.lua file with two items a and b.
|
||||||
|
rm(file("history.lua"))
|
||||||
|
touch(test_file("a"))
|
||||||
|
touch(test_file("b"))
|
||||||
|
local h = reload()
|
||||||
|
h:addItem(test_file("a"))
|
||||||
|
h:addItem(test_file("b"))
|
||||||
|
mv(file("history.lua"), file("history.backup"))
|
||||||
|
|
||||||
|
h = reload()
|
||||||
|
assert.is.same(#h.hist, 0)
|
||||||
|
mv(file("history.backup"), file("history.lua"))
|
||||||
|
h:reload()
|
||||||
|
|
||||||
|
assert.is.same(#h.hist, 2)
|
||||||
|
assert_item_is(h, 1, "a")
|
||||||
|
assert_item_is(h, 2, "b")
|
||||||
|
|
||||||
|
rm(test_file("a"))
|
||||||
|
rm(test_file("b"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function testAutoRemoveDeletedItems()
|
||||||
|
-- Prepare a history.lua file with two items a and b.
|
||||||
|
rm(file("history.lua"))
|
||||||
|
touch(test_file("a"))
|
||||||
|
touch(test_file("b"))
|
||||||
|
local h = reload()
|
||||||
|
h:addItem(test_file("a"))
|
||||||
|
h:addItem(test_file("b"))
|
||||||
|
|
||||||
|
rm(test_file("a"))
|
||||||
|
|
||||||
|
h:reload()
|
||||||
|
assert.is.same(#h.hist, 1)
|
||||||
|
assert_item_is(h, 1, "b")
|
||||||
|
|
||||||
|
rm(test_file("b"))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function testDoNotAutoRemoveDeletedItems()
|
||||||
|
-- Prepare a history.lua file with two items a and b.
|
||||||
|
rm(file("history.lua"))
|
||||||
|
touch(test_file("a"))
|
||||||
|
touch(test_file("b"))
|
||||||
|
local h = reload()
|
||||||
|
h:addItem(test_file("a"))
|
||||||
|
h:addItem(test_file("b"))
|
||||||
|
|
||||||
|
rm(test_file("a"))
|
||||||
|
|
||||||
|
h:reload()
|
||||||
|
assert.is.same(#h.hist, 2)
|
||||||
|
assert_item_is(h, 1, "a", true)
|
||||||
|
assert_item_is(h, 2, "b")
|
||||||
|
|
||||||
|
rm(test_file("b"))
|
||||||
|
end
|
||||||
|
|
||||||
|
it("should automatically remove deleted items from history if setting has been set",
|
||||||
|
function()
|
||||||
|
G_reader_settings:saveSetting("autoremove_deleted_items_from_history", "true")
|
||||||
|
testAutoRemoveDeletedItems()
|
||||||
|
G_reader_settings:delSetting("autoremove_deleted_items_from_history")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should not automatically remove deleted items from history if setting has not been set",
|
||||||
|
function()
|
||||||
|
G_reader_settings:delSetting("autoremove_deleted_items_from_history")
|
||||||
|
testDoNotAutoRemoveDeletedItems()
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should not automatically remove deleted items from history if setting has been set to false",
|
||||||
|
function()
|
||||||
|
G_reader_settings:saveSetting("autoremove_deleted_items_from_history", "false")
|
||||||
|
testDoNotAutoRemoveDeletedItems()
|
||||||
|
G_reader_settings:delSetting("autoremove_deleted_items_from_history")
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user