mirror of
https://github.com/koreader/koreader
synced 2024-11-04 12:00:25 +00:00
492 lines
17 KiB
Lua
492 lines
17 KiB
Lua
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
local LoginDialog = require("ui/widget/logindialog")
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
local NetworkMgr = require("ui/network/manager")
|
|
local DataStorage = require("datastorage")
|
|
local DocSettings = require("docsettings")
|
|
local UIManager = require("ui/uimanager")
|
|
local Screen = require("device").screen
|
|
local util = require("ffi/util")
|
|
local Device = require("device")
|
|
local DEBUG = require("dbg")
|
|
local T = require("ffi/util").template
|
|
local _ = require("gettext")
|
|
local slt2 = require('slt2')
|
|
local MyClipping = require("clip")
|
|
local realpath = require("ffi/util").realpath
|
|
|
|
local EvernoteExporter = InputContainer:new{
|
|
name = "evernote",
|
|
login_title = _("Login to Evernote"),
|
|
notebook_name = _("KOReader Notes"),
|
|
evernote_domain = nil,
|
|
notemarks = _("Note: "),
|
|
clipping_dir = DataStorage:getDataDir() .. "/clipboard",
|
|
|
|
evernote_token = nil,
|
|
notebook_guid = nil,
|
|
}
|
|
|
|
function EvernoteExporter:init()
|
|
self.ui.menu:registerToMainMenu(self)
|
|
|
|
local settings = G_reader_settings:readSetting("evernote") or {}
|
|
self.evernote_domain = settings.domain
|
|
self.evernote_username = settings.username or ""
|
|
self.evernote_token = settings.token
|
|
self.notebook_guid = settings.notebook
|
|
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{
|
|
my_clippings = "/mnt/us/documents/My Clippings.txt",
|
|
history_dir = "./history",
|
|
}
|
|
self.template = slt2.loadfile(self.path.."/note.tpl")
|
|
self:migrateClippings()
|
|
self.config = DocSettings:open(util.joinPath(self.clipping_dir, "evernote.sdr"))
|
|
end
|
|
|
|
function EvernoteExporter:isDocless()
|
|
return self.ui == nil or self.ui.document == nil or self.view == nil
|
|
end
|
|
|
|
function EvernoteExporter:readyToExport()
|
|
return self.evernote_token ~= nil or self.html_export ~= false or self.txt_export ~= false
|
|
end
|
|
|
|
function EvernoteExporter:migrateClippings()
|
|
local old_dir = util.joinPath(util.realpath(util.joinPath(self.path, "..")),
|
|
"evernote.sdr")
|
|
if lfs.attributes(old_dir, "mode") == "directory" then
|
|
local mv_bin = Device:isAndroid() and "/system/bin/mv" or "/bin/mv"
|
|
return util.execute(mv_bin, old_dir, self.clipping_dir) == 0
|
|
end
|
|
end
|
|
|
|
function EvernoteExporter:addToMainMenu(tab_item_table)
|
|
table.insert(tab_item_table.plugins, {
|
|
text = _("Evernote"),
|
|
sub_item_table = {
|
|
{
|
|
text_func = function()
|
|
local domain
|
|
if self.evernote_domain == "sandbox" then
|
|
domain = "Sandbox"
|
|
elseif self.evernote_domain == "yinxiang" then
|
|
domain = "Yinxiang"
|
|
else
|
|
domain = "Evernote"
|
|
end
|
|
return self.evernote_token and (_("Logout") .. " " .. domain)
|
|
or _("Login")
|
|
end,
|
|
callback_func = function()
|
|
return self.evernote_token and function() self:logout() end
|
|
or nil
|
|
end,
|
|
sub_item_table_func = function()
|
|
return not self.evernote_token and {
|
|
{
|
|
text = "Evernote",
|
|
callback = function()
|
|
self.evernote_domain = nil
|
|
self:login()
|
|
end
|
|
},
|
|
{
|
|
text = "印象笔记",
|
|
callback = function()
|
|
self.evernote_domain = "yinxiang"
|
|
self:login()
|
|
end
|
|
}
|
|
} or nil
|
|
end,
|
|
},
|
|
{
|
|
text = _("Export all notes in this book"),
|
|
enabled_func = function()
|
|
return not self:isDocless() and self:readyToExport()
|
|
end,
|
|
callback = function()
|
|
UIManager:scheduleIn(0.5, function()
|
|
self:exportCurrentNotes(self.view)
|
|
end)
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Exporting may take several seconds…"),
|
|
timeout = 1,
|
|
})
|
|
end
|
|
},
|
|
{
|
|
text = _("Export all notes in your library"),
|
|
enabled_func = function()
|
|
return self:readyToExport()
|
|
end,
|
|
callback = function()
|
|
UIManager:scheduleIn(0.5, function()
|
|
self:exportAllNotes()
|
|
end)
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Exporting may take several minutes…"),
|
|
timeout = 1,
|
|
})
|
|
end
|
|
},
|
|
{
|
|
text = _("Export to local HTML files"),
|
|
checked_func = function() return self.html_export end,
|
|
callback = function()
|
|
self.html_export = not self.html_export
|
|
if self.html_export then self.txt_export = false end
|
|
self:saveSettings()
|
|
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 have been purged.\nAll notes will be exported again next time.\nIt is recommended to remove the existing KOReaderClipping.txt file to avoid duplication."),
|
|
})
|
|
end
|
|
}
|
|
}
|
|
})
|
|
end
|
|
|
|
function EvernoteExporter:login()
|
|
if not NetworkMgr:isOnline() then
|
|
NetworkMgr:promptWifiOn()
|
|
end
|
|
self.login_dialog = LoginDialog:new{
|
|
title = self.login_title,
|
|
username = self.evernote_username or "",
|
|
buttons = {
|
|
{
|
|
{
|
|
text = _("Cancel"),
|
|
enabled = true,
|
|
callback = function()
|
|
self:closeDialog()
|
|
end,
|
|
},
|
|
{
|
|
text = _("Login"),
|
|
enabled = true,
|
|
callback = function()
|
|
local username, password = self:getCredential()
|
|
self:closeDialog()
|
|
UIManager:scheduleIn(0.5, function()
|
|
self:doLogin(username, password)
|
|
end)
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Logging in. Please wait…"),
|
|
timeout = 1,
|
|
})
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
width = Screen:getWidth() * 0.8,
|
|
height = Screen:getHeight() * 0.4,
|
|
}
|
|
|
|
self.login_dialog:onShowKeyboard()
|
|
UIManager:show(self.login_dialog)
|
|
end
|
|
|
|
function EvernoteExporter:closeDialog()
|
|
self.login_dialog:onClose()
|
|
UIManager:close(self.login_dialog)
|
|
end
|
|
|
|
function EvernoteExporter:getCredential()
|
|
return self.login_dialog:getCredential()
|
|
end
|
|
|
|
function EvernoteExporter:doLogin(username, password)
|
|
local EvernoteOAuth = require("EvernoteOAuth")
|
|
local EvernoteClient = require("EvernoteClient")
|
|
|
|
local oauth = EvernoteOAuth:new{
|
|
domain = self.evernote_domain,
|
|
username = username,
|
|
password = password,
|
|
}
|
|
self.evernote_username = username
|
|
local ok, token = pcall(oauth.getToken, oauth)
|
|
-- prompt users to turn on Wifi if network is unreachable
|
|
if not ok and token then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("An error occurred while logging in:") .. "\n" .. token,
|
|
})
|
|
return
|
|
end
|
|
|
|
local client = EvernoteClient:new{
|
|
domain = self.evernote_domain,
|
|
authToken = token,
|
|
}
|
|
local guid
|
|
ok, guid = pcall(self.getExportNotebook, self, client)
|
|
if not ok and guid and guid:find("Transport not open") then
|
|
NetworkMgr:promptWifiOn()
|
|
return
|
|
elseif not ok and guid then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("An error occurred while logging in:") .. "\n" .. guid,
|
|
})
|
|
elseif ok and guid then
|
|
self.evernote_token = token
|
|
self.notebook_guid = guid
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Logged in to Evernote."),
|
|
})
|
|
end
|
|
|
|
self:saveSettings()
|
|
end
|
|
|
|
function EvernoteExporter:logout()
|
|
self.evernote_token = nil
|
|
self.notebook_guid = nil
|
|
self.evernote_domain = nil
|
|
self:saveSettings()
|
|
end
|
|
|
|
function EvernoteExporter:saveSettings()
|
|
local settings = {
|
|
domain = self.evernote_domain,
|
|
username = self.evernote_username,
|
|
token = self.evernote_token,
|
|
notebook = self.notebook_guid,
|
|
html_export = self.html_export,
|
|
txt_export = self.txt_export,
|
|
}
|
|
G_reader_settings:saveSetting("evernote", settings)
|
|
end
|
|
|
|
function EvernoteExporter:getExportNotebook(client)
|
|
local name = self.notebook_name
|
|
return client:findNotebookByTitle(name) or client:createNotebook(name).guid
|
|
end
|
|
|
|
function EvernoteExporter:exportCurrentNotes(view)
|
|
local clippings = self.parser:parseCurrentDoc(view)
|
|
self:exportClippings(clippings)
|
|
end
|
|
|
|
function EvernoteExporter:updateHistoryClippings(clippings, new_clippings)
|
|
-- update clippings from history clippings
|
|
for title, booknotes in pairs(new_clippings) do
|
|
for chapter_index, chapternotes in ipairs(booknotes) do
|
|
for note_index, note in ipairs(chapternotes) do
|
|
if clippings[title] == nil or clippings[title][chapter_index] == nil
|
|
or clippings[title][chapter_index][note_index] == nil
|
|
or clippings[title][chapter_index][note_index].page ~= note.page
|
|
or clippings[title][chapter_index][note_index].time ~= note.time
|
|
or clippings[title][chapter_index][note_index].text ~= note.text
|
|
or clippings[title][chapter_index][note_index].note ~= note.note then
|
|
DEBUG("found new notes in history", booknotes.title)
|
|
clippings[title] = booknotes
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return clippings
|
|
end
|
|
|
|
function EvernoteExporter:updateMyClippings(clippings, new_clippings)
|
|
-- only new titles or new notes in My clippings are updated to clippings
|
|
-- since appending is the only way to modify notes in My Clippings
|
|
for title, booknotes in pairs(new_clippings) do
|
|
if clippings[title] == nil or #clippings[title] < #booknotes then
|
|
DEBUG("found new notes in MyClipping", booknotes.title)
|
|
clippings[title] = booknotes
|
|
end
|
|
end
|
|
return clippings
|
|
end
|
|
|
|
function EvernoteExporter:exportAllNotes()
|
|
local clippings = self.config:readSetting("clippings") or {}
|
|
clippings = self:updateHistoryClippings(clippings, self.parser:parseHistory())
|
|
clippings = self:updateMyClippings(clippings, self.parser:parseMyClippings())
|
|
-- remove blank entries
|
|
for title, booknotes in pairs(clippings) do
|
|
-- chapter number is zero
|
|
if #booknotes == 0 then
|
|
clippings[title] = nil
|
|
end
|
|
end
|
|
--DEBUG("clippings", clippings)
|
|
self:exportClippings(clippings)
|
|
self.config:saveSetting("clippings", clippings)
|
|
self.config:flush()
|
|
end
|
|
|
|
function EvernoteExporter:exportClippings(clippings)
|
|
local client = nil
|
|
local exported_stamp
|
|
if not self.html_export and not self.txt_export then
|
|
client = require("EvernoteClient"):new{
|
|
domain = self.evernote_domain,
|
|
authToken = self.evernote_token,
|
|
}
|
|
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
|
|
|
|
local export_count, error_count = 0, 0
|
|
local export_title, error_title
|
|
for title, booknotes in pairs(clippings) do
|
|
if type(booknotes.exported) ~= "table" then
|
|
booknotes.exported = {}
|
|
end
|
|
-- check if booknotes are exported in this notebook
|
|
-- so that booknotes will still be exported after switching user account
|
|
if booknotes.exported[exported_stamp] ~= true then
|
|
local ok, err
|
|
if self.html_export then
|
|
ok, err = pcall(self.exportBooknotesToHTML, self, title, booknotes)
|
|
elseif self.txt_export then
|
|
ok, err = pcall(self.exportBooknotesToTXT, self, title, booknotes)
|
|
else
|
|
ok, err = pcall(self.exportBooknotesToEvernote, self, client, title, booknotes)
|
|
end
|
|
-- error reporting
|
|
if not ok and err and err:find("Transport not open") then
|
|
NetworkMgr:promptWifiOn()
|
|
return
|
|
elseif not ok and err then
|
|
DEBUG("Error occurs when exporting book:", title, err)
|
|
error_count = error_count + 1
|
|
error_title = title
|
|
elseif ok then
|
|
DEBUG("Exported notes in book:", title)
|
|
export_count = export_count + 1
|
|
export_title = title
|
|
booknotes.exported[exported_stamp] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
local msg = "Nothing was exported."
|
|
local all_count = export_count + error_count
|
|
if export_count > 0 and error_count == 0 then
|
|
if all_count == 1 then
|
|
msg = _("Exported notes from book:") .. "\n" .. export_title
|
|
else
|
|
msg = T(
|
|
_("Exported notes from book:\n%1\nand %2 others."),
|
|
export_title,
|
|
all_count-1
|
|
)
|
|
end
|
|
elseif error_count > 0 then
|
|
if all_count == 1 then
|
|
msg = _("An error occurred while trying to export notes from book:") .. "\n" .. error_title
|
|
else
|
|
msg = T(
|
|
_("Multiple errors occurred while trying to export notes from book:\n%1\nand %2 others."),
|
|
error_title,
|
|
error_count-1
|
|
)
|
|
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 })
|
|
end
|
|
|
|
function EvernoteExporter:exportBooknotesToEvernote(client, title, booknotes)
|
|
local content = slt2.render(self.template, {
|
|
booknotes = booknotes,
|
|
notemarks = self.notemarks,
|
|
})
|
|
--DEBUG("content", content)
|
|
local note_guid = client:findNoteByTitle(title, self.notebook_guid)
|
|
local resources = {}
|
|
for _, chapter in ipairs(booknotes) do
|
|
for _, clipping in ipairs(chapter) do
|
|
if clipping.image then
|
|
table.insert(resources, {
|
|
image = clipping.image
|
|
})
|
|
-- nullify clipping image after passing it to evernote client
|
|
clipping.image = nil
|
|
end
|
|
end
|
|
end
|
|
if not note_guid then
|
|
client:createNote(title, content, resources, {}, self.notebook_guid)
|
|
else
|
|
client:updateNote(note_guid, title, content, resources, {}, self.notebook_guid)
|
|
end
|
|
end
|
|
|
|
function EvernoteExporter:exportBooknotesToHTML(title, booknotes)
|
|
local content = slt2.render(self.template, {
|
|
booknotes = booknotes,
|
|
notemarks = self.notemarks,
|
|
})
|
|
--DEBUG("content", content)
|
|
local html = io.open(self.clipping_dir .. "/" .. title .. ".html", "w")
|
|
if html then
|
|
html:write(content)
|
|
html:close()
|
|
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
|