mirror of
https://github.com/koreader/koreader.git
synced 2025-12-13 20:36:53 +01:00
evernote: ReadHistory integration and text file output (#2498)
This commit is contained in:
@@ -17,6 +17,7 @@ end
|
|||||||
|
|
||||||
-- Sidecar directory is the file without _last_ suffix.
|
-- Sidecar directory is the file without _last_ suffix.
|
||||||
function DocSettings:getSidecarDir(doc_path)
|
function DocSettings:getSidecarDir(doc_path)
|
||||||
|
if doc_path == nil or doc_path == '' then return '' end
|
||||||
local file_without_suffix = doc_path:match("(.*)%.")
|
local file_without_suffix = doc_path:match("(.*)%.")
|
||||||
if file_without_suffix then
|
if file_without_suffix then
|
||||||
return file_without_suffix..".sdr"
|
return file_without_suffix..".sdr"
|
||||||
@@ -26,6 +27,11 @@ function DocSettings:getSidecarDir(doc_path)
|
|||||||
return doc_path..".sdr"
|
return doc_path..".sdr"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function DocSettings:getSidecarFile(doc_path)
|
||||||
|
if doc_path == nil or doc_path == '' then return '' end
|
||||||
|
return self:getSidecarDir(doc_path) .. "/" .. doc_path:match(".*%.(.+)") .. ".lua"
|
||||||
|
end
|
||||||
|
|
||||||
function DocSettings:hasSidecarDir(doc_path)
|
function DocSettings:hasSidecarDir(doc_path)
|
||||||
-- We may be called with items from FileManager, which may not be a document
|
-- We may be called with items from FileManager, which may not be a document
|
||||||
if lfs.attributes(doc_path, "mode") == "directory" then
|
if lfs.attributes(doc_path, "mode") == "directory" then
|
||||||
@@ -39,24 +45,30 @@ function DocSettings:getHistoryPath(fullpath)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function DocSettings:getPathFromHistory(hist_name)
|
function DocSettings:getPathFromHistory(hist_name)
|
||||||
if hist_name == nil or hist_name == '' then return nil end
|
if hist_name == nil or hist_name == '' then return '' end
|
||||||
-- 1. select everything included in brackets
|
-- 1. select everything included in brackets
|
||||||
local s = string.match(hist_name,"%b[]")
|
local s = string.match(hist_name,"%b[]")
|
||||||
if s == nil or s == '' then return nil end
|
if s == nil or s == '' then return '' end
|
||||||
-- 2. crop the bracket-sign from both sides
|
-- 2. crop the bracket-sign from both sides
|
||||||
-- 3. and finally replace decorative signs '#' to dir-char '/'
|
-- 3. and finally replace decorative signs '#' to dir-char '/'
|
||||||
return string.gsub(string.sub(s,2,-3),"#","/")
|
return string.gsub(string.sub(s,2,-3),"#","/")
|
||||||
end
|
end
|
||||||
|
|
||||||
function DocSettings:getNameFromHistory(hist_name)
|
function DocSettings:getNameFromHistory(hist_name)
|
||||||
if hist_name == nil or hist_name == '' then return nil end
|
if hist_name == nil or hist_name == '' then return '' end
|
||||||
local s = string.match(hist_name, "%b[]")
|
local s = string.match(hist_name, "%b[]")
|
||||||
if s == nil or s == '' then return nil end
|
if s == nil or s == '' then return '' end
|
||||||
-- at first, search for path length
|
-- at first, search for path length
|
||||||
-- and return the rest of string without 4 last characters (".lua")
|
-- and return the rest of string without 4 last characters (".lua")
|
||||||
return string.sub(hist_name, string.len(s)+2, -5)
|
return string.sub(hist_name, string.len(s)+2, -5)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function DocSettings:ensureSidecar(sidecar)
|
||||||
|
if lfs.attributes(sidecar, "mode") ~= "directory" then
|
||||||
|
lfs.mkdir(sidecar)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function DocSettings:open(docfile)
|
function DocSettings:open(docfile)
|
||||||
-- TODO(zijiehe): Remove history_path, use only sidecar.
|
-- TODO(zijiehe): Remove history_path, use only sidecar.
|
||||||
local new = {}
|
local new = {}
|
||||||
@@ -65,16 +77,13 @@ function DocSettings:open(docfile)
|
|||||||
|
|
||||||
local sidecar = self:getSidecarDir(docfile)
|
local sidecar = self:getSidecarDir(docfile)
|
||||||
new.sidecar = sidecar
|
new.sidecar = sidecar
|
||||||
if lfs.attributes(sidecar, "mode") ~= "directory" then
|
DocSettings:ensureSidecar(sidecar)
|
||||||
lfs.mkdir(sidecar)
|
|
||||||
end
|
|
||||||
-- If there is a file which has a same name as the sidecar directory, or
|
-- 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.
|
-- the file system is read-only, we should not waste time to read it.
|
||||||
if lfs.attributes(sidecar, "mode") == "directory" then
|
if lfs.attributes(sidecar, "mode") == "directory" then
|
||||||
-- New sidecar file name is metadata.{file last suffix}.lua. So we
|
-- New sidecar file name is metadata.{file last suffix}.lua. So we
|
||||||
-- can handle two files with only different suffixes.
|
-- can handle two files with only different suffixes.
|
||||||
new.sidecar_file = sidecar.."/metadata."..
|
new.sidecar_file = self:getSidecarFile(docfile)
|
||||||
docfile:match(".*%.(.+)")..".lua"
|
|
||||||
new.legacy_sidecar_file = sidecar.."/"..
|
new.legacy_sidecar_file = sidecar.."/"..
|
||||||
docfile:match("([^%/]+%..+)")..".lua"
|
docfile:match("([^%/]+%..+)")..".lua"
|
||||||
end
|
end
|
||||||
@@ -136,6 +145,7 @@ function DocSettings:flush()
|
|||||||
-- If we can write to sidecar_file, we do not need to write to history_file
|
-- If we can write to sidecar_file, we do not need to write to history_file
|
||||||
-- anymore.
|
-- anymore.
|
||||||
local serials = { self.sidecar_file, self.history_file }
|
local serials = { self.sidecar_file, self.history_file }
|
||||||
|
self:ensureSidecar(self.sidecar)
|
||||||
local s_out = dump(self.data)
|
local s_out = dump(self.data)
|
||||||
os.setlocale('C', 'numeric')
|
os.setlocale('C', 'numeric')
|
||||||
for _, f in pairs(serials) do
|
for _, f in pairs(serials) do
|
||||||
@@ -174,6 +184,7 @@ function DocSettings:purge()
|
|||||||
if lfs.attributes(self.sidecar, "mode") == "directory" then
|
if lfs.attributes(self.sidecar, "mode") == "directory" then
|
||||||
purgeDir(self.sidecar)
|
purgeDir(self.sidecar)
|
||||||
end
|
end
|
||||||
|
self.data = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
return DocSettings
|
return DocSettings
|
||||||
|
|||||||
@@ -252,4 +252,18 @@ function util.replaceSlashChar(str)
|
|||||||
return str:gsub('%/','_')
|
return str:gsub('%/','_')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Split a file into its path and name
|
||||||
|
function util.splitFilePathName(file)
|
||||||
|
if file == nil or file == "" then return "", "" end
|
||||||
|
if string.find(file, "/") == nil then return "", file end
|
||||||
|
return string.gsub(file, "(.*/)(.*)", "%1"), string.gsub(file, ".*/", "")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Split a file name into its pure file name and suffix
|
||||||
|
function util.splitFileNameSuffix(file)
|
||||||
|
if file == nil or file == "" then return "", "" end
|
||||||
|
if string.find(file, "%.") == nil then return file, "" end
|
||||||
|
return string.gsub(file, "(.*)%.(.*)", "%1"), string.gsub(file, ".*%.", "")
|
||||||
|
end
|
||||||
|
|
||||||
return util
|
return util
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
local DocumentRegistry = require("document/documentregistry")
|
local DocumentRegistry = require("document/documentregistry")
|
||||||
local DocSettings = require("docsettings")
|
local DocSettings = require("docsettings")
|
||||||
|
local ReadHistory = require("readhistory")
|
||||||
local md5 = require("ffi/MD5")
|
local md5 = require("ffi/MD5")
|
||||||
|
local util = require("util")
|
||||||
|
|
||||||
local MyClipping = {
|
local MyClipping = {
|
||||||
my_clippings = "/mnt/us/documents/My Clippings.txt",
|
my_clippings = "/mnt/us/documents/My Clippings.txt",
|
||||||
@@ -254,25 +256,37 @@ function MyClipping:parseHighlight(highlights, book)
|
|||||||
table.sort(book, function(v1, v2) return v1[1].page < v2[1].page end)
|
table.sort(book, function(v1, v2) return v1[1].page < v2[1].page end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function MyClipping:parseHistoryFile(clippings, history_file, doc_file)
|
||||||
|
if lfs.attributes(history_file, "mode") ~= "file"
|
||||||
|
or not history_file:find(".+%.lua$") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if lfs.attributes(doc_file, "mode") ~= "file" then return end
|
||||||
|
local ok, stored = pcall(dofile, history_file)
|
||||||
|
if ok and stored.highlight then
|
||||||
|
local _, docname = util.splitFilePathName(doc_file)
|
||||||
|
local title, author = self:getTitle(util.splitFileNameSuffix(docname))
|
||||||
|
clippings[title] = {
|
||||||
|
file = doc_file,
|
||||||
|
title = title,
|
||||||
|
author = author,
|
||||||
|
}
|
||||||
|
self:parseHighlight(stored.highlight, clippings[title])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function MyClipping:parseHistory()
|
function MyClipping:parseHistory()
|
||||||
local clippings = {}
|
local clippings = {}
|
||||||
for f in lfs.dir(self.history_dir) do
|
for f in lfs.dir(self.history_dir) do
|
||||||
local path = self.history_dir.."/"..f
|
self:parseHistoryFile(clippings,
|
||||||
if lfs.attributes(path, "mode") == "file" and path:find(".+%.lua$") then
|
self.history_dir .. "/" .. f,
|
||||||
local ok, stored = pcall(dofile, path)
|
DocSettings:getPathFromHistory(f) .. "/" ..
|
||||||
if ok and stored.highlight then
|
DocSettings:getNameFromHistory(f))
|
||||||
local _, _, docname = path:find("%[.*%](.*)%.lua$")
|
end
|
||||||
local title, author = self:getTitle(docname)
|
for _, item in ipairs(ReadHistory.hist) do
|
||||||
local docpath = DocSettings:getPathFromHistory(f)
|
self:parseHistoryFile(clippings,
|
||||||
local name = DocSettings:getNameFromHistory(f)
|
DocSettings:getSidecarFile(item.file),
|
||||||
clippings[title] = {
|
item.file)
|
||||||
file = docpath .. "/" .. name,
|
|
||||||
title = title,
|
|
||||||
author = author,
|
|
||||||
}
|
|
||||||
self:parseHighlight(stored.highlight, clippings[title])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return clippings
|
return clippings
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ local T = require("ffi/util").template
|
|||||||
local _ = require("gettext")
|
local _ = require("gettext")
|
||||||
local slt2 = require('slt2')
|
local slt2 = require('slt2')
|
||||||
local MyClipping = require("clip")
|
local MyClipping = require("clip")
|
||||||
|
local realpath = require("ffi/util").realpath
|
||||||
|
|
||||||
local EvernoteExporter = InputContainer:new{
|
local EvernoteExporter = InputContainer:new{
|
||||||
name = "evernote",
|
name = "evernote",
|
||||||
@@ -35,6 +36,11 @@ function EvernoteExporter:init()
|
|||||||
self.evernote_token = settings.token
|
self.evernote_token = settings.token
|
||||||
self.notebook_guid = settings.notebook
|
self.notebook_guid = settings.notebook
|
||||||
self.html_export = settings.html_export or false
|
self.html_export = settings.html_export or false
|
||||||
|
if self.html_export then
|
||||||
|
self.txt_export = false
|
||||||
|
else
|
||||||
|
self.txt_export = settings.txt_export or false
|
||||||
|
end
|
||||||
|
|
||||||
self.parser = MyClipping:new{
|
self.parser = MyClipping:new{
|
||||||
my_clippings = "/mnt/us/documents/My Clippings.txt",
|
my_clippings = "/mnt/us/documents/My Clippings.txt",
|
||||||
@@ -50,7 +56,7 @@ function EvernoteExporter:isDocless()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function EvernoteExporter:readyToExport()
|
function EvernoteExporter:readyToExport()
|
||||||
return self.evernote_token ~= nil or self.html_export ~= false
|
return self.evernote_token ~= nil or self.html_export ~= false or self.txt_export ~= false
|
||||||
end
|
end
|
||||||
|
|
||||||
function EvernoteExporter:migrateClippings()
|
function EvernoteExporter:migrateClippings()
|
||||||
@@ -139,9 +145,28 @@ function EvernoteExporter:addToMainMenu(tab_item_table)
|
|||||||
checked_func = function() return self.html_export end,
|
checked_func = function() return self.html_export end,
|
||||||
callback = function()
|
callback = function()
|
||||||
self.html_export = not self.html_export
|
self.html_export = not self.html_export
|
||||||
|
if self.html_export then self.txt_export = false end
|
||||||
self:saveSettings()
|
self:saveSettings()
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text = _("Export to local clipping text file"),
|
||||||
|
checked_func = function() return self.txt_export end,
|
||||||
|
callback = function()
|
||||||
|
self.txt_export = not self.txt_export
|
||||||
|
if self.txt_export then self.html_export = false end
|
||||||
|
self:saveSettings()
|
||||||
|
end
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("Purge history records"),
|
||||||
|
callback = function()
|
||||||
|
self.config:purge()
|
||||||
|
UIManager:show(InfoMessage:new{
|
||||||
|
text = _("History records are purged.\nAll notes will be exported again next time.\nSuggest to remove existing KOReaderClipping.txt to avoid a duplication."),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
@@ -254,6 +279,7 @@ function EvernoteExporter:saveSettings()
|
|||||||
token = self.evernote_token,
|
token = self.evernote_token,
|
||||||
notebook = self.notebook_guid,
|
notebook = self.notebook_guid,
|
||||||
html_export = self.html_export,
|
html_export = self.html_export,
|
||||||
|
txt_export = self.txt_export,
|
||||||
}
|
}
|
||||||
G_reader_settings:saveSetting("evernote", settings)
|
G_reader_settings:saveSetting("evernote", settings)
|
||||||
end
|
end
|
||||||
@@ -319,13 +345,19 @@ end
|
|||||||
|
|
||||||
function EvernoteExporter:exportClippings(clippings)
|
function EvernoteExporter:exportClippings(clippings)
|
||||||
local client = nil
|
local client = nil
|
||||||
local exported_stamp = "html"
|
local exported_stamp
|
||||||
if not self.html_export then
|
if not self.html_export and not self.txt_export then
|
||||||
client = require("EvernoteClient"):new{
|
client = require("EvernoteClient"):new{
|
||||||
domain = self.evernote_domain,
|
domain = self.evernote_domain,
|
||||||
authToken = self.evernote_token,
|
authToken = self.evernote_token,
|
||||||
}
|
}
|
||||||
exported_stamp = self.notebook_guid
|
exported_stamp = self.notebook_guid
|
||||||
|
elseif self.html_export then
|
||||||
|
exported_stamp= "html"
|
||||||
|
elseif self.txt_export then
|
||||||
|
exported_stamp = "txt"
|
||||||
|
else
|
||||||
|
assert("an exported_stamp is expected for a new export type")
|
||||||
end
|
end
|
||||||
|
|
||||||
local export_count, error_count = 0, 0
|
local export_count, error_count = 0, 0
|
||||||
@@ -339,11 +371,11 @@ function EvernoteExporter:exportClippings(clippings)
|
|||||||
if booknotes.exported[exported_stamp] ~= true then
|
if booknotes.exported[exported_stamp] ~= true then
|
||||||
local ok, err
|
local ok, err
|
||||||
if self.html_export then
|
if self.html_export then
|
||||||
ok, err = pcall(self.exportBooknotesToHTML, self,
|
ok, err = pcall(self.exportBooknotesToHTML, self, title, booknotes)
|
||||||
title, booknotes)
|
elseif self.txt_export then
|
||||||
|
ok, err = pcall(self.exportBooknotesToTXT, self, title, booknotes)
|
||||||
else
|
else
|
||||||
ok, err = pcall(self.exportBooknotesToEvernote, self,
|
ok, err = pcall(self.exportBooknotesToEvernote, self, client, title, booknotes)
|
||||||
client, title, booknotes)
|
|
||||||
end
|
end
|
||||||
-- error reporting
|
-- error reporting
|
||||||
if not ok and err and err:find("Transport not open") then
|
if not ok and err and err:find("Transport not open") then
|
||||||
@@ -385,6 +417,9 @@ function EvernoteExporter:exportClippings(clippings)
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if self.html_export or self.txt_export then
|
||||||
|
msg = msg .. T(_("\nNotes can be found in %1/."), realpath(self.clipping_dir))
|
||||||
|
end
|
||||||
UIManager:show(InfoMessage:new{ text = msg })
|
UIManager:show(InfoMessage:new{ text = msg })
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -427,4 +462,30 @@ function EvernoteExporter:exportBooknotesToHTML(title, booknotes)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function EvernoteExporter:exportBooknotesToTXT(title, booknotes)
|
||||||
|
local file = io.open(self.clipping_dir .. "/KOReaderClipping.txt", "a")
|
||||||
|
if file then
|
||||||
|
file:write(title .. "\n")
|
||||||
|
for _ignore1, chapter in ipairs(booknotes) do
|
||||||
|
if chapter.title then
|
||||||
|
file:write(" - " .. chapter.title .. "\n\n")
|
||||||
|
end
|
||||||
|
for _ignore2, clipping in ipairs(chapter) do
|
||||||
|
file:write(T(_(" -- Page: %1, added on %2\n\n"),
|
||||||
|
clipping.page, os.date("%c", clipping.time)))
|
||||||
|
if clipping.text then
|
||||||
|
file:write(clipping.text)
|
||||||
|
end
|
||||||
|
if clipping.image then
|
||||||
|
file:write(_("<An image>"))
|
||||||
|
end
|
||||||
|
file:write("\n==========\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
file:write("\n")
|
||||||
|
file:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return EvernoteExporter
|
return EvernoteExporter
|
||||||
|
|||||||
@@ -189,6 +189,37 @@ describe("util module", function()
|
|||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("should split file path and name", function()
|
||||||
|
local test = function(full, path, name)
|
||||||
|
local p, n = util.splitFilePathName(full)
|
||||||
|
assert.are_same(p, path)
|
||||||
|
assert.are_same(n, name)
|
||||||
|
end
|
||||||
|
test("/a/b/c.txt", "/a/b/", "c.txt")
|
||||||
|
test("/a/b////c.txt", "/a/b////", "c.txt")
|
||||||
|
test("/a/b/", "/a/b/", "")
|
||||||
|
test("c.txt", "", "c.txt")
|
||||||
|
test("", "", "")
|
||||||
|
test(nil, "", "")
|
||||||
|
test("a/b", "a/", "b")
|
||||||
|
test("/b", "/", "b")
|
||||||
|
assert.are_same(util.splitFilePathName("/a/b/c.txt"), "/a/b/")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should split file name and suffix", function()
|
||||||
|
local test = function(full, name, suffix)
|
||||||
|
local n, s = util.splitFileNameSuffix(full)
|
||||||
|
assert.are_same(n, name)
|
||||||
|
assert.are_same(s, suffix)
|
||||||
|
end
|
||||||
|
test("a.txt", "a", "txt")
|
||||||
|
test("/a/b.txt", "/a/b", "txt")
|
||||||
|
test("a", "a", "")
|
||||||
|
test("/a/b", "/a/b", "")
|
||||||
|
test("/a/", "/a/", "")
|
||||||
|
test("/a/.txt", "/a/", "txt")
|
||||||
|
test(nil, "", "")
|
||||||
|
test("", "", "")
|
||||||
|
assert.are_same(util.splitFileNameSuffix("a.txt"), "a")
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user