ReadHistory: support reload (#3003)

Allows ReadHistory to reload according to the modification timestamp of history.lua.
This commit is contained in:
Hzj_jie
2017-07-19 05:56:17 -07:00
committed by Frans de Jonge
parent f95c52f680
commit d6845d94b1
2 changed files with 141 additions and 20 deletions

View File

@@ -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

View File

@@ -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)