mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Docsettings: add centralized sdr storage (#10074)
Added an option to choose a new location to save document settings, highlights and bookmarks (koreader/docsettings folder).
This commit is contained in:
@@ -42,6 +42,9 @@ function DataStorage:getSettingsDir()
|
||||
return self:getDataDir() .. "/settings"
|
||||
end
|
||||
|
||||
function DataStorage:getDocSettingsDir()
|
||||
return self:getDataDir() .. "/docsettings"
|
||||
end
|
||||
|
||||
function DataStorage:getFullDataDir()
|
||||
if full_data_dir then return full_data_dir end
|
||||
@@ -58,8 +61,8 @@ end
|
||||
local function initDataDir()
|
||||
local sub_data_dirs = {
|
||||
"cache", "clipboard",
|
||||
"data", "data/dict", "data/tessdata",
|
||||
"history", "ota", "plugins",
|
||||
"data", "data/dict", "data/tessdata", "docsettings",
|
||||
"history", "ota", "patches", "plugins",
|
||||
"screenshots", "settings", "styletweaks",
|
||||
}
|
||||
for _, dir in ipairs(sub_data_dirs) do
|
||||
|
||||
@@ -15,6 +15,7 @@ local util = require("util")
|
||||
local DocSettings = LuaSettings:extend{}
|
||||
|
||||
local HISTORY_DIR = DataStorage:getHistoryDir()
|
||||
local DOCSETTINGS_DIR = DataStorage:getDocSettingsDir()
|
||||
|
||||
local function buildCandidates(list)
|
||||
local candidates = {}
|
||||
@@ -64,120 +65,125 @@ local function buildCandidates(list)
|
||||
end
|
||||
|
||||
--- Returns path to sidecar directory (`filename.sdr`).
|
||||
--
|
||||
-- Sidecar directory is the file without _last_ suffix.
|
||||
-- @string doc_path path to the document (e.g., `/foo/bar.pdf`)
|
||||
-- @treturn string path to the sidecar directory (e.g., `/foo/bar.sdr`)
|
||||
function DocSettings:getSidecarDir(doc_path)
|
||||
if doc_path == nil or doc_path == '' then return '' end
|
||||
local file_without_suffix = doc_path:match("(.*)%.")
|
||||
if file_without_suffix then
|
||||
return file_without_suffix..".sdr"
|
||||
function DocSettings:getSidecarDir(doc_path, force_location)
|
||||
if doc_path == nil or doc_path == "" then return "" end
|
||||
local path = doc_path:match("(.*)%.") or doc_path -- file path without the last suffix
|
||||
local location = force_location or G_reader_settings:readSetting("document_metadata_folder", "doc")
|
||||
if location == "dir" then
|
||||
path = DOCSETTINGS_DIR..path
|
||||
end
|
||||
return doc_path..".sdr"
|
||||
return path..".sdr"
|
||||
end
|
||||
|
||||
--- Returns path to `metadata.lua` file.
|
||||
-- @string doc_path path to the document (e.g., `/foo/bar.pdf`)
|
||||
-- @treturn string path to `/foo/bar.sdr/metadata.lua` file
|
||||
function DocSettings:getSidecarFile(doc_path)
|
||||
if doc_path == nil or doc_path == '' then return '' end
|
||||
function DocSettings:getSidecarFile(doc_path, force_location)
|
||||
if doc_path == nil or doc_path == "" then return "" end
|
||||
-- If the file does not have a suffix or we are working on a directory, we
|
||||
-- should ignore the suffix part in metadata file path.
|
||||
local suffix = doc_path:match(".*%.(.+)")
|
||||
if suffix == nil then
|
||||
suffix = ''
|
||||
end
|
||||
return self:getSidecarDir(doc_path) .. "/metadata." .. suffix .. ".lua"
|
||||
local suffix = doc_path:match(".*%.(.+)") or ""
|
||||
return self:getSidecarDir(doc_path, force_location) .. "/metadata." .. suffix .. ".lua"
|
||||
end
|
||||
|
||||
--- Returns `true` if there is a `metadata.lua` file.
|
||||
-- @string doc_path path to the document (e.g., `/foo/bar.pdf`)
|
||||
-- @treturn bool
|
||||
function DocSettings:hasSidecarFile(doc_path)
|
||||
return lfs.attributes(self:getSidecarFile(doc_path), "mode") == "file"
|
||||
return lfs.attributes(self:getSidecarFile(doc_path, "doc"), "mode") == "file"
|
||||
or lfs.attributes(self:getSidecarFile(doc_path, "dir"), "mode") == "file"
|
||||
or lfs.attributes(self:getHistoryPath(doc_path), "mode") == "file"
|
||||
end
|
||||
|
||||
function DocSettings:getHistoryPath(fullpath)
|
||||
return HISTORY_DIR .. "/[" .. fullpath:gsub("(.*/)([^/]+)", "%1] %2"):gsub("/", "#") .. ".lua"
|
||||
function DocSettings:getLastSaveTime(doc_path) -- for readhistory
|
||||
return lfs.attributes(self:getSidecarFile(doc_path, "doc"), "modification")
|
||||
or lfs.attributes(self:getSidecarFile(doc_path, "dir"), "modification")
|
||||
end
|
||||
|
||||
function DocSettings:getHistoryPath(doc_path)
|
||||
if doc_path == nil or doc_path == "" then return "" end
|
||||
return HISTORY_DIR .. "/[" .. doc_path:gsub("(.*/)([^/]+)", "%1] %2"):gsub("/", "#") .. ".lua"
|
||||
end
|
||||
|
||||
function DocSettings:getPathFromHistory(hist_name)
|
||||
if hist_name == nil or hist_name == '' then return '' end
|
||||
if hist_name:sub(-4) ~= ".lua" then return '' end -- ignore .lua.old backups
|
||||
if hist_name == nil or hist_name == "" then return "" end
|
||||
if hist_name:sub(-4) ~= ".lua" then return "" end -- ignore .lua.old backups
|
||||
-- 1. select everything included in brackets
|
||||
local s = string.match(hist_name,"%b[]")
|
||||
if s == nil or s == '' then return '' end
|
||||
if s == nil or s == "" then return "" end
|
||||
-- 2. crop the bracket-sign from both sides
|
||||
-- 3. and finally replace decorative signs '#' to dir-char '/'
|
||||
return string.gsub(string.sub(s, 2, -3), "#", "/")
|
||||
end
|
||||
|
||||
function DocSettings:getNameFromHistory(hist_name)
|
||||
if hist_name == nil or hist_name == '' then return '' end
|
||||
if hist_name:sub(-4) ~= ".lua" then return '' end -- ignore .lua.old backups
|
||||
if hist_name == nil or hist_name == "" then return "" end
|
||||
if hist_name:sub(-4) ~= ".lua" then return "" end -- ignore .lua.old backups
|
||||
local s = string.match(hist_name, "%b[]")
|
||||
if s == nil or s == '' then return '' end
|
||||
if s == nil or s == "" then return "" end
|
||||
-- at first, search for path length
|
||||
-- and return the rest of string without 4 last characters (".lua")
|
||||
return string.sub(hist_name, string.len(s)+2, -5)
|
||||
end
|
||||
|
||||
function DocSettings:getLastSaveTime(doc_path)
|
||||
local attr = lfs.attributes(self:getSidecarFile(doc_path))
|
||||
if attr and attr.mode == "file" then
|
||||
return attr.modification
|
||||
end
|
||||
end
|
||||
|
||||
function DocSettings:ensureSidecar(sidecar)
|
||||
if lfs.attributes(sidecar, "mode") ~= "directory" then
|
||||
lfs.mkdir(sidecar)
|
||||
function DocSettings:getFileFromHistory(hist_name)
|
||||
local path = self:getPathFromHistory(hist_name)
|
||||
if path ~= "" then
|
||||
local name = self:getNameFromHistory(hist_name)
|
||||
if name ~= "" then
|
||||
return ffiutil.joinPath(path, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Opens a document's individual settings (font, margin, dictionary, etc.)
|
||||
-- @string docfile path to the document (e.g., `/foo/bar.pdf`)
|
||||
-- @string doc_path path to the document (e.g., `/foo/bar.pdf`)
|
||||
-- @treturn DocSettings object
|
||||
function DocSettings:open(docfile)
|
||||
--- @todo (zijiehe): Remove history_path, use only sidecar.
|
||||
|
||||
function DocSettings:open(doc_path)
|
||||
-- NOTE: Beware, our new instance is new, but self is still DocSettings!
|
||||
local new = DocSettings:extend{}
|
||||
new.history_file = new:getHistoryPath(docfile)
|
||||
|
||||
local sidecar = new:getSidecarDir(docfile)
|
||||
new.sidecar = sidecar
|
||||
DocSettings:ensureSidecar(sidecar)
|
||||
-- If there is a file which has a same name as the sidecar directory,
|
||||
-- or the file system is read-only, we should not waste time to read it.
|
||||
if lfs.attributes(sidecar, "mode") == "directory" then
|
||||
-- New sidecar file name is metadata.{file last suffix}.lua.
|
||||
-- So we can handle two files with only different suffixes.
|
||||
new.sidecar_file = new:getSidecarFile(docfile)
|
||||
new.legacy_sidecar_file = sidecar.."/"..
|
||||
ffiutil.basename(docfile)..".lua"
|
||||
new.doc_sidecar_dir = new:getSidecarDir(doc_path, "doc")
|
||||
new.doc_sidecar_file = new:getSidecarFile(doc_path, "doc")
|
||||
local doc_sidecar_file, legacy_sidecar_file
|
||||
if lfs.attributes(new.doc_sidecar_dir, "mode") == "directory" then
|
||||
doc_sidecar_file = new.doc_sidecar_file
|
||||
legacy_sidecar_file = new.doc_sidecar_dir.."/"..ffiutil.basename(doc_path)..".lua"
|
||||
end
|
||||
new.dir_sidecar_dir = new:getSidecarDir(doc_path, "dir")
|
||||
new.dir_sidecar_file = new:getSidecarFile(doc_path, "dir")
|
||||
local dir_sidecar_file
|
||||
if lfs.attributes(new.dir_sidecar_dir, "mode") == "directory" then
|
||||
dir_sidecar_file = new.dir_sidecar_file
|
||||
end
|
||||
local history_file = new:getHistoryPath(doc_path)
|
||||
|
||||
-- Candidates list, in order of priority:
|
||||
local candidates_list = {
|
||||
-- New sidecar file
|
||||
new.sidecar_file or "",
|
||||
-- Backup file of new sidecar file
|
||||
new.sidecar_file and (new.sidecar_file .. ".old") or "",
|
||||
-- New sidecar file in doc folder
|
||||
doc_sidecar_file or "",
|
||||
-- Backup file of new sidecar file in doc folder
|
||||
doc_sidecar_file and (doc_sidecar_file..".old") or "",
|
||||
-- Legacy sidecar file
|
||||
new.legacy_sidecar_file or "",
|
||||
legacy_sidecar_file or "",
|
||||
-- New sidecar file in docsettings folder
|
||||
dir_sidecar_file or "",
|
||||
-- Backup file of new sidecar file in docsettings folder
|
||||
dir_sidecar_file and (dir_sidecar_file..".old") or "",
|
||||
-- Legacy history folder
|
||||
new.history_file,
|
||||
history_file,
|
||||
-- Backup file in legacy history folder
|
||||
new.history_file .. ".old",
|
||||
history_file..".old",
|
||||
-- Legacy kpdfview setting
|
||||
docfile..".kpdfview.lua",
|
||||
doc_path..".kpdfview.lua",
|
||||
}
|
||||
-- We get back an array of tables for *existing* candidates, sorted MRU first (insertion order breaks ties).
|
||||
local candidates = buildCandidates(candidates_list)
|
||||
|
||||
local ok, stored, filepath
|
||||
local ok, stored
|
||||
for _, t in ipairs(candidates) do
|
||||
local candidate_path = t.path
|
||||
-- Ignore empty files
|
||||
@@ -186,7 +192,6 @@ function DocSettings:open(docfile)
|
||||
-- Ignore empty tables
|
||||
if ok and next(stored) ~= nil then
|
||||
logger.dbg("DocSettings: data is read from", candidate_path)
|
||||
filepath = candidate_path
|
||||
break
|
||||
end
|
||||
end
|
||||
@@ -196,7 +201,6 @@ function DocSettings:open(docfile)
|
||||
if ok and stored then
|
||||
new.data = stored
|
||||
new.candidates = candidates
|
||||
new.filepath = filepath
|
||||
else
|
||||
new.data = {}
|
||||
end
|
||||
@@ -205,40 +209,33 @@ function DocSettings:open(docfile)
|
||||
end
|
||||
|
||||
--- Serializes settings and writes them to `metadata.lua`.
|
||||
function DocSettings:flush()
|
||||
-- write serialized version of the data table into one of
|
||||
-- i) sidecar directory in the same directory of the document or
|
||||
-- ii) history directory in root directory of KOReader
|
||||
if not self.history_file and not self.sidecar_file then
|
||||
return
|
||||
end
|
||||
function DocSettings:flush(data)
|
||||
-- Depending on the settings, doc_settings are saved to the book folder or
|
||||
-- to koreader/docsettings folder. The latter is also a fallback for read-only book storage.
|
||||
local serials = G_reader_settings:readSetting("document_metadata_folder", "doc") == "doc"
|
||||
and { {self.doc_sidecar_dir, self.doc_sidecar_file},
|
||||
{self.dir_sidecar_dir, self.dir_sidecar_file}, }
|
||||
or { {self.dir_sidecar_dir, self.dir_sidecar_file}, }
|
||||
|
||||
-- If we can write to sidecar_file, we do not need to write to history_file anymore.
|
||||
local serials = {}
|
||||
if self.sidecar_file then
|
||||
table.insert(serials, self.sidecar_file)
|
||||
end
|
||||
if self.history_file then
|
||||
table.insert(serials, self.history_file)
|
||||
end
|
||||
self:ensureSidecar(self.sidecar)
|
||||
local s_out = dump(self.data)
|
||||
for _, f in ipairs(serials) do
|
||||
local s_out = dump(data or self.data, nil, true)
|
||||
for _, s in ipairs(serials) do
|
||||
util.makePath(s[1])
|
||||
local sidecar_file = s[2]
|
||||
local directory_updated = false
|
||||
if lfs.attributes(f, "mode") == "file" then
|
||||
if lfs.attributes(sidecar_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(f, "modification")
|
||||
local mtime = lfs.attributes(sidecar_file, "modification")
|
||||
if mtime < os.time() - 60 then
|
||||
logger.dbg("DocSettings: Renamed", f, "to", f .. ".old")
|
||||
os.rename(f, f .. ".old")
|
||||
logger.dbg("DocSettings: Renamed", sidecar_file, "to", sidecar_file .. ".old")
|
||||
os.rename(sidecar_file, sidecar_file .. ".old")
|
||||
directory_updated = true -- fsync directory content too below
|
||||
end
|
||||
end
|
||||
logger.dbg("DocSettings: Writing to", f)
|
||||
local f_out = io.open(f, "w")
|
||||
logger.dbg("DocSettings: Writing to", sidecar_file)
|
||||
local f_out = io.open(sidecar_file, "w")
|
||||
if f_out ~= nil then
|
||||
f_out:write("-- we can read Lua syntax here!\nreturn ")
|
||||
f_out:write(s_out)
|
||||
@@ -246,77 +243,67 @@ function DocSettings:flush()
|
||||
ffiutil.fsyncOpenedFile(f_out) -- force flush to the storage device
|
||||
f_out:close()
|
||||
|
||||
if self.candidates ~= nil
|
||||
and G_reader_settings:nilOrFalse("preserve_legacy_docsetting") then
|
||||
for _, t in ipairs(self.candidates) do
|
||||
local candidate_path = t.path
|
||||
if candidate_path ~= f and candidate_path ~= f .. ".old" then
|
||||
logger.dbg("DocSettings: Removed legacy file", candidate_path)
|
||||
os.remove(candidate_path)
|
||||
-- We should not remove sidecar folder, as it may
|
||||
-- contain Kindle history files.
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if directory_updated then
|
||||
-- Ensure the file renaming is flushed to storage device
|
||||
ffiutil.fsyncDirectory(f)
|
||||
ffiutil.fsyncDirectory(sidecar_file)
|
||||
end
|
||||
|
||||
self:purge(false, sidecar_file) -- remove old candidates and empty sidecar folders
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DocSettings:getFilePath()
|
||||
return self.filepath
|
||||
end
|
||||
|
||||
--- Purges (removes) sidecar directory.
|
||||
function DocSettings:purge(full)
|
||||
function DocSettings:purge(full, sidecar_to_keep)
|
||||
-- Remove any of the old ones we may consider as candidates in DocSettings:open()
|
||||
if self.history_file then
|
||||
os.remove(self.history_file)
|
||||
os.remove(self.history_file .. ".old")
|
||||
end
|
||||
if self.legacy_sidecar_file then
|
||||
os.remove(self.legacy_sidecar_file)
|
||||
end
|
||||
if lfs.attributes(self.sidecar, "mode") == "directory" then
|
||||
if full then
|
||||
-- Asked to remove all the content of this .sdr directory, whether it's ours or not
|
||||
ffiutil.purgeDir(self.sidecar)
|
||||
else
|
||||
-- Only remove the files we know we may have created with our usual names.
|
||||
for f in lfs.dir(self.sidecar) do
|
||||
local fullpath = self.sidecar.."/"..f
|
||||
local to_remove = false
|
||||
if lfs.attributes(fullpath, "mode") == "file" then
|
||||
-- Currently, we only create a single file in there,
|
||||
-- named metadata.suffix.lua (ie. metadata.epub.lua),
|
||||
-- with possibly backups named metadata.epub.lua.old and
|
||||
-- metadata.epub.lua.old_dom20180528,
|
||||
-- so all sharing the same base: self.sidecar_file
|
||||
if util.stringStartsWith(fullpath, self.sidecar_file) then
|
||||
to_remove = true
|
||||
end
|
||||
end
|
||||
if to_remove then
|
||||
os.remove(fullpath)
|
||||
logger.dbg("DocSettings: purged:", fullpath)
|
||||
if self.candidates then
|
||||
for _, t in ipairs(self.candidates) do
|
||||
local candidate_path = t.path
|
||||
if lfs.attributes(candidate_path, "mode") == "file" then
|
||||
if (not sidecar_to_keep)
|
||||
or (candidate_path ~= sidecar_to_keep and candidate_path ~= sidecar_to_keep..".old") then
|
||||
os.remove(candidate_path)
|
||||
logger.dbg("DocSettings: purged:", candidate_path)
|
||||
end
|
||||
end
|
||||
-- If the sidecar folder ends up empty, os.remove() can delete it.
|
||||
-- Otherwise, the following statement has no effect.
|
||||
os.remove(self.sidecar)
|
||||
end
|
||||
end
|
||||
-- We should have meet the candidate we used and remove it above.
|
||||
-- But in case we didn't, remove it.
|
||||
if self.filepath and lfs.attributes(self.filepath, "mode") == "file" then
|
||||
os.remove(self.filepath)
|
||||
|
||||
local function purgeDir(dir, full_purge)
|
||||
if lfs.attributes(dir, "mode") == "directory" then
|
||||
if full_purge then
|
||||
-- Asked to remove all the content of this .sdr directory, whether it's ours or not
|
||||
ffiutil.purgeDir(dir)
|
||||
else
|
||||
-- If the sidecar folder ends up empty, os.remove() can delete it.
|
||||
-- Otherwise, the following statement has no effect.
|
||||
os.remove(dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
purgeDir(self.doc_sidecar_dir, full)
|
||||
purgeDir(self.dir_sidecar_dir, full)
|
||||
end
|
||||
|
||||
--- Updates sidecar info for file rename/copy/move/delete operations.
|
||||
function DocSettings:update(doc_path, new_doc_path, copy)
|
||||
if self:hasSidecarFile(doc_path) then
|
||||
local doc_settings = DocSettings:open(doc_path)
|
||||
if new_doc_path then
|
||||
local new_doc_settings = DocSettings:open(new_doc_path)
|
||||
new_doc_settings:flush(doc_settings.data) -- with current "Book metadata folder" setting
|
||||
else
|
||||
local cache_file_path = doc_settings:readSetting("cache_file_path")
|
||||
if cache_file_path then
|
||||
os.remove(cache_file_path)
|
||||
end
|
||||
end
|
||||
if not copy then
|
||||
doc_settings:purge()
|
||||
end
|
||||
end
|
||||
self.data = {}
|
||||
end
|
||||
|
||||
return DocSettings
|
||||
|
||||
@@ -146,17 +146,13 @@ function ReadHistory:_readLegacyHistory()
|
||||
local history_updated
|
||||
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
|
||||
local item_path = joinPath(path, file)
|
||||
local item_time = lfs.attributes(joinPath(history_dir, f), "modification")
|
||||
if self:addItem(item_path, item_time, true) then
|
||||
history_updated = true
|
||||
end
|
||||
local legacy_history_file = joinPath(history_dir, f)
|
||||
if lfs.attributes(legacy_history_file, "mode") == "file" then
|
||||
local item_path = DocSettings:getFileFromHistory(f)
|
||||
if item_path then
|
||||
local item_time = lfs.attributes(legacy_history_file, "modification")
|
||||
if self:addItem(item_path, item_time, true) then
|
||||
history_updated = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -526,6 +526,34 @@ common_settings.document = {
|
||||
-- submenus are filled by menu_order
|
||||
}
|
||||
|
||||
local metadata_folder_str = {
|
||||
["doc"] = _("book folder"),
|
||||
["dir"] = "koreader/docsettings",
|
||||
}
|
||||
|
||||
local function genMetadataFolderMenuItem(value)
|
||||
return {
|
||||
text = metadata_folder_str[value],
|
||||
checked_func = function()
|
||||
return G_reader_settings:readSetting("document_metadata_folder") == value
|
||||
end,
|
||||
callback = function()
|
||||
G_reader_settings:saveSetting("document_metadata_folder", value)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
common_settings.document_metadata_folder = {
|
||||
text_func = function()
|
||||
local value = G_reader_settings:readSetting("document_metadata_folder", "doc")
|
||||
return T(_("Book metadata folder: %1"), metadata_folder_str[value])
|
||||
end,
|
||||
sub_item_table = {
|
||||
genMetadataFolderMenuItem("doc"),
|
||||
genMetadataFolderMenuItem("dir"),
|
||||
},
|
||||
}
|
||||
|
||||
common_settings.document_auto_save = {
|
||||
text_func = function()
|
||||
local interval = G_reader_settings:readSetting("auto_save_settings_interval_minutes")
|
||||
@@ -552,6 +580,7 @@ common_settings.document_auto_save = {
|
||||
end,
|
||||
} or nil,
|
||||
},
|
||||
separator = true,
|
||||
}
|
||||
|
||||
common_settings.document_save = {
|
||||
|
||||
@@ -36,6 +36,7 @@ local order = {
|
||||
-- end common settings
|
||||
},
|
||||
document = {
|
||||
"document_metadata_folder",
|
||||
"document_auto_save",
|
||||
"document_save",
|
||||
"document_end_action",
|
||||
|
||||
@@ -79,6 +79,7 @@ local order = {
|
||||
"status_bar",
|
||||
},
|
||||
document = {
|
||||
"document_metadata_folder",
|
||||
"document_auto_save",
|
||||
"document_save",
|
||||
"document_end_action",
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
local DataStorage = require("datastorage")
|
||||
local DocumentRegistry = require("document/documentregistry")
|
||||
local DocSettings = require("docsettings")
|
||||
local ReadHistory = require("readhistory")
|
||||
local ffiutil = require("ffi/util")
|
||||
local lfs = require("libs/libkoreader-lfs")
|
||||
local logger = require("logger")
|
||||
local md5 = require("ffi/sha2").md5
|
||||
local util = require("util")
|
||||
local _ = require("gettext")
|
||||
local T = require("ffi/util").template
|
||||
local T = ffiutil.template
|
||||
|
||||
local MyClipping = {
|
||||
my_clippings = "/mnt/us/documents/My Clippings.txt",
|
||||
history_dir = "./history",
|
||||
}
|
||||
|
||||
function MyClipping:new(o)
|
||||
@@ -333,18 +333,22 @@ end
|
||||
|
||||
function MyClipping:parseHistory()
|
||||
local clippings = {}
|
||||
for f in lfs.dir(self.history_dir) do
|
||||
self:parseHistoryFile(clippings,
|
||||
self.history_dir .. "/" .. f,
|
||||
DocSettings:getPathFromHistory(f) .. "/" ..
|
||||
DocSettings:getNameFromHistory(f))
|
||||
local history_dir = DataStorage:getHistoryDir()
|
||||
for f in lfs.dir(history_dir) do
|
||||
local legacy_history_file = ffiutil.joinPath(history_dir, f)
|
||||
if lfs.attributes(legacy_history_file, "mode") == "file" then
|
||||
local doc_file = DocSettings:getFileFromHistory(f)
|
||||
if doc_file then
|
||||
self:parseHistoryFile(clippings, legacy_history_file, doc_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, item in ipairs(ReadHistory.hist) do
|
||||
self:parseHistoryFile(clippings,
|
||||
DocSettings:getSidecarFile(item.file),
|
||||
item.file)
|
||||
for _, item in ipairs(require("readhistory").hist) do
|
||||
if not item.dim then
|
||||
self:parseHistoryFile(clippings, DocSettings:getSidecarFile(item.file, "doc"), item.file)
|
||||
self:parseHistoryFile(clippings, DocSettings:getSidecarFile(item.file, "dir"), item.file)
|
||||
end
|
||||
end
|
||||
|
||||
return clippings
|
||||
end
|
||||
|
||||
|
||||
@@ -1,20 +1,38 @@
|
||||
describe("docsettings module", function()
|
||||
local docsettings, lfs, util
|
||||
local DataStorage, docsettings, docsettings_dir, ffiutil, lfs
|
||||
|
||||
setup(function()
|
||||
require("commonrequire")
|
||||
DataStorage = require("datastorage")
|
||||
docsettings = require("docsettings")
|
||||
ffiutil = require("ffi/util")
|
||||
lfs = require("libs/libkoreader-lfs")
|
||||
util = require("ffi/util")
|
||||
|
||||
docsettings_dir = DataStorage:getDocSettingsDir()
|
||||
end)
|
||||
|
||||
it("should generate sidecar directory path", function()
|
||||
it("should generate sidecar folder path in book folder (by default)", function()
|
||||
G_reader_settings:delSetting("document_metadata_folder")
|
||||
assert.Equals("../../foo.sdr", docsettings:getSidecarDir("../../foo.pdf"))
|
||||
assert.Equals("/foo/bar.sdr", docsettings:getSidecarDir("/foo/bar.pdf"))
|
||||
assert.Equals("baz.sdr", docsettings:getSidecarDir("baz.pdf"))
|
||||
end)
|
||||
|
||||
it("should generate sidecar metadata file", function()
|
||||
it("should generate sidecar folder path in book folder", function()
|
||||
G_reader_settings:saveSetting("document_metadata_folder", "doc")
|
||||
assert.Equals("../../foo.sdr", docsettings:getSidecarDir("../../foo.pdf"))
|
||||
assert.Equals("/foo/bar.sdr", docsettings:getSidecarDir("/foo/bar.pdf"))
|
||||
assert.Equals("baz.sdr", docsettings:getSidecarDir("baz.pdf"))
|
||||
end)
|
||||
|
||||
it("should generate sidecar folder path in docsettings folder", function()
|
||||
G_reader_settings:saveSetting("document_metadata_folder", "dir")
|
||||
assert.Equals(docsettings_dir.."/foo/bar.sdr", docsettings:getSidecarDir("/foo/bar.pdf"))
|
||||
assert.Equals(docsettings_dir.."baz.sdr", docsettings:getSidecarDir("baz.pdf"))
|
||||
end)
|
||||
|
||||
it("should generate sidecar metadata file (book folder)", function()
|
||||
G_reader_settings:saveSetting("document_metadata_folder", "doc")
|
||||
assert.Equals("../../foo.sdr/metadata.pdf.lua",
|
||||
docsettings:getSidecarFile("../../foo.pdf"))
|
||||
assert.Equals("/foo/bar.sdr/metadata.pdf.lua",
|
||||
@@ -23,7 +41,16 @@ describe("docsettings module", function()
|
||||
docsettings:getSidecarFile("baz.epub"))
|
||||
end)
|
||||
|
||||
it("should generate sidecar metadata file (docsettings folder)", function()
|
||||
G_reader_settings:saveSetting("document_metadata_folder", "dir")
|
||||
assert.Equals(docsettings_dir.."/foo/bar.sdr/metadata.pdf.lua",
|
||||
docsettings:getSidecarFile("/foo/bar.pdf"))
|
||||
assert.Equals(docsettings_dir.."baz.sdr/metadata.epub.lua",
|
||||
docsettings:getSidecarFile("baz.epub"))
|
||||
end)
|
||||
|
||||
it("should read legacy history file", function()
|
||||
G_reader_settings:delSetting("document_metadata_folder")
|
||||
local file = "file.pdf"
|
||||
local d = docsettings:open(file)
|
||||
d:saveSetting("a", "b")
|
||||
@@ -32,15 +59,15 @@ describe("docsettings module", function()
|
||||
-- Now the sidecar file should be written.
|
||||
|
||||
local legacy_files = {
|
||||
d.history_file,
|
||||
d.sidecar .. "/file.pdf.lua",
|
||||
docsettings:getHistoryPath(file),
|
||||
d.doc_sidecar_dir .. "/file.pdf.lua",
|
||||
"file.pdf.kpdfview.lua",
|
||||
}
|
||||
|
||||
for _, f in pairs(legacy_files) do
|
||||
assert.False(os.rename(d.sidecar_file, f) == nil)
|
||||
for _, f in ipairs(legacy_files) do
|
||||
assert.False(os.rename(d.doc_sidecar_file, f) == nil)
|
||||
d = docsettings:open(file)
|
||||
assert.True(os.remove(d.sidecar_file) == nil)
|
||||
assert.True(os.remove(d.doc_sidecar_file) == nil)
|
||||
-- Legacy history files should not be removed before flush has been
|
||||
-- called.
|
||||
assert.Equals(lfs.attributes(f, "mode"), "file")
|
||||
@@ -53,7 +80,7 @@ describe("docsettings module", function()
|
||||
assert.True(os.remove(f) == nil)
|
||||
end
|
||||
|
||||
assert.False(os.remove(d.sidecar_file) == nil)
|
||||
assert.False(os.remove(d.doc_sidecar_file) == nil)
|
||||
d:purge()
|
||||
end)
|
||||
|
||||
@@ -62,20 +89,20 @@ describe("docsettings module", function()
|
||||
local d = docsettings:open(file)
|
||||
|
||||
local legacy_files = {
|
||||
d.history_file,
|
||||
d.sidecar .. "/file.pdf.lua",
|
||||
docsettings:getHistoryPath(file),
|
||||
d.doc_sidecar_dir .. "/file.pdf.lua",
|
||||
"file.pdf.kpdfview.lua",
|
||||
}
|
||||
|
||||
-- docsettings:flush will remove legacy files.
|
||||
for i, v in pairs(legacy_files) do
|
||||
for i, v in ipairs(legacy_files) do
|
||||
d:saveSetting("a", i)
|
||||
d:flush()
|
||||
assert.False(os.rename(d.sidecar_file, v.."1") == nil)
|
||||
assert.False(os.rename(d.doc_sidecar_file, v.."1") == nil)
|
||||
end
|
||||
|
||||
d:close()
|
||||
for _, v in pairs(legacy_files) do
|
||||
for _, v in ipairs(legacy_files) do
|
||||
assert.False(os.rename(v.."1", v) == nil)
|
||||
end
|
||||
|
||||
@@ -87,45 +114,46 @@ describe("docsettings module", function()
|
||||
|
||||
it("should build correct legacy history path", function()
|
||||
local file = "/a/b/c--d/c.txt"
|
||||
local history_path = util.basename(docsettings:getHistoryPath(file))
|
||||
local history_path = ffiutil.basename(docsettings:getHistoryPath(file))
|
||||
local path_from_history = docsettings:getPathFromHistory(history_path)
|
||||
local name_from_history = docsettings:getNameFromHistory(history_path)
|
||||
assert.is.same(file, path_from_history .. "/" .. name_from_history)
|
||||
end)
|
||||
|
||||
it("should reserve last good file", function()
|
||||
G_reader_settings:delSetting("document_metadata_folder")
|
||||
local file = "file.pdf"
|
||||
local d = docsettings:open(file)
|
||||
d:saveSetting("a", "a")
|
||||
d:flush()
|
||||
-- metadata.pdf.lua should be generated.
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file, "mode"))
|
||||
d:flush()
|
||||
-- metadata.pdf.lua.old should not yet be generated.
|
||||
assert.are.not_equal("file", lfs.attributes(d.sidecar_file .. ".old", "mode"))
|
||||
assert.are.not_equal("file", lfs.attributes(d.doc_sidecar_file .. ".old", "mode"))
|
||||
-- make metadata.pdf.lua older to bypass 60s age needed for .old rotation
|
||||
local minutes_ago = os.time() - 120
|
||||
lfs.touch(d.sidecar_file, minutes_ago)
|
||||
lfs.touch(d.doc_sidecar_file, minutes_ago)
|
||||
d:close()
|
||||
-- metadata.pdf.lua and metadata.pdf.lua.old should be generated.
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file .. ".old", "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file .. ".old", "mode"))
|
||||
|
||||
-- write some garbage to sidecar-file.
|
||||
local f_out = io.open(d.sidecar_file, "w")
|
||||
local f_out = io.open(d.doc_sidecar_file, "w")
|
||||
f_out:write("bla bla bla")
|
||||
f_out:close()
|
||||
|
||||
d = docsettings:open(file)
|
||||
-- metadata.pdf.lua should be removed.
|
||||
assert.are.not_equal("file", lfs.attributes(d.sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file .. ".old", "mode"))
|
||||
assert.are.not_equal("file", lfs.attributes(d.doc_sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file .. ".old", "mode"))
|
||||
assert.Equals("a", d:readSetting("a"))
|
||||
d:saveSetting("a", "b")
|
||||
d:close()
|
||||
-- metadata.pdf.lua should be generated.
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file .. ".old", "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file .. ".old", "mode"))
|
||||
-- The contents in sidecar_file and sidecar_file.old are different.
|
||||
-- a:b v.s. a:a
|
||||
|
||||
@@ -133,87 +161,89 @@ describe("docsettings module", function()
|
||||
-- The content should come from sidecar_file.
|
||||
assert.Equals("b", d:readSetting("a"))
|
||||
-- write some garbage to sidecar-file.
|
||||
f_out = io.open(d.sidecar_file, "w")
|
||||
f_out = io.open(d.doc_sidecar_file, "w")
|
||||
f_out:write("bla bla bla")
|
||||
f_out:close()
|
||||
|
||||
-- do not flush the result, open docsettings again.
|
||||
d = docsettings:open(file)
|
||||
-- metadata.pdf.lua should be removed.
|
||||
assert.are.not_equal("file", lfs.attributes(d.sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file .. ".old", "mode"))
|
||||
assert.are.not_equal("file", lfs.attributes(d.doc_sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file .. ".old", "mode"))
|
||||
-- The content should come from sidecar_file.old.
|
||||
assert.Equals("a", d:readSetting("a"))
|
||||
d:close()
|
||||
-- metadata.pdf.lua should be generated.
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file .. ".old", "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file .. ".old", "mode"))
|
||||
end)
|
||||
|
||||
describe("ignore empty sidecar file", function()
|
||||
it("should ignore empty file", function()
|
||||
G_reader_settings:delSetting("document_metadata_folder")
|
||||
local file = "file.pdf"
|
||||
local d = docsettings:open(file)
|
||||
d:saveSetting("a", "a")
|
||||
d:flush()
|
||||
-- metadata.pdf.lua should be generated.
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file, "mode"))
|
||||
-- make metadata.pdf.lua older to bypass 60s age needed for .old rotation
|
||||
local minutes_ago = os.time() - 120
|
||||
lfs.touch(d.sidecar_file, minutes_ago)
|
||||
lfs.touch(d.doc_sidecar_file, minutes_ago)
|
||||
d:close()
|
||||
-- metadata.pdf.lua and metadata.pdf.lua.old should be generated.
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file .. ".old", "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file .. ".old", "mode"))
|
||||
|
||||
-- reset the sidecar_file to an empty file.
|
||||
local f_out = io.open(d.sidecar_file, "w")
|
||||
local f_out = io.open(d.doc_sidecar_file, "w")
|
||||
f_out:close()
|
||||
|
||||
d = docsettings:open(file)
|
||||
-- metadata.pdf.lua should be removed.
|
||||
assert.are.not_equal("file", lfs.attributes(d.sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file .. ".old", "mode"))
|
||||
assert.are.not_equal("file", lfs.attributes(d.doc_sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file .. ".old", "mode"))
|
||||
assert.Equals("a", d:readSetting("a"))
|
||||
d:saveSetting("a", "b")
|
||||
d:close()
|
||||
-- metadata.pdf.lua should be generated.
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file .. ".old", "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file .. ".old", "mode"))
|
||||
-- The contents in sidecar_file and sidecar_file.old are different.
|
||||
-- a:b v.s. a:a
|
||||
end)
|
||||
|
||||
it("should ignore empty table", function()
|
||||
G_reader_settings:delSetting("document_metadata_folder")
|
||||
local file = "file.pdf"
|
||||
local d = docsettings:open(file)
|
||||
d:saveSetting("a", "a")
|
||||
d:flush()
|
||||
-- metadata.pdf.lua should be generated.
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file, "mode"))
|
||||
-- make metadata.pdf.lua older to bypass 60s age needed for .old rotation
|
||||
local minutes_ago = os.time() - 120
|
||||
lfs.touch(d.sidecar_file, minutes_ago)
|
||||
lfs.touch(d.doc_sidecar_file, minutes_ago)
|
||||
d:close()
|
||||
-- metadata.pdf.lua and metadata.pdf.lua.old should be generated.
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file .. ".old", "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file .. ".old", "mode"))
|
||||
|
||||
-- reset the sidecar_file to an empty file.
|
||||
local f_out = io.open(d.sidecar_file, "w")
|
||||
local f_out = io.open(d.doc_sidecar_file, "w")
|
||||
f_out:write("{ } ")
|
||||
f_out:close()
|
||||
|
||||
d = docsettings:open(file)
|
||||
-- metadata.pdf.lua should be removed.
|
||||
assert.are.not_equal("file", lfs.attributes(d.sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file .. ".old", "mode"))
|
||||
assert.are.not_equal("file", lfs.attributes(d.doc_sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file .. ".old", "mode"))
|
||||
assert.Equals("a", d:readSetting("a"))
|
||||
d:saveSetting("a", "b")
|
||||
d:close()
|
||||
-- metadata.pdf.lua should be generated.
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.sidecar_file .. ".old", "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file, "mode"))
|
||||
assert.Equals("file", lfs.attributes(d.doc_sidecar_file .. ".old", "mode"))
|
||||
-- The contents in sidecar_file and sidecar_file.old are different.
|
||||
-- a:b v.s. a:a
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user