973 lines
32 KiB
Lua
973 lines
32 KiB
Lua
local ConfirmBox = require("ui/widget/confirmbox")
|
|
local Device = require("device")
|
|
local Event = require("ui/event")
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
local InputDialog = require("ui/widget/inputdialog")
|
|
local Math = require("optmath")
|
|
local NetworkMgr = require("ui/network/manager")
|
|
local SpinWidget = require("ui/widget/spinwidget")
|
|
local UIManager = require("ui/uimanager")
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
|
local json = require("json")
|
|
local logger = require("logger")
|
|
local sha2 = require("ffi/sha2")
|
|
local time = require("ui/time")
|
|
local util = require("util")
|
|
local T = require("ffi/util").template
|
|
local _ = require("gettext")
|
|
|
|
local BookhoardAPI = require("BookhoardAPI")
|
|
|
|
local SYNC_STRATEGY = {
|
|
PROMPT = 1,
|
|
SILENT = 2,
|
|
DISABLE = 3,
|
|
}
|
|
|
|
local SYNC_MODE = {
|
|
IMMEDIATE = "immediate",
|
|
CHECKPOINT = "checkpoint",
|
|
}
|
|
|
|
local API_CALL_DEBOUNCE_DELAY = time.s(25)
|
|
local PERIODIC_PUSH_DELAY = 10
|
|
|
|
local sha256hex = sha2.sha256hex or sha2.sha256
|
|
|
|
local Bookhoard = WidgetContainer:extend({
|
|
name = "bookhoard",
|
|
is_doc_only = false,
|
|
title = _("Bookhoard Server"),
|
|
settings_key = "bookhoard",
|
|
|
|
push_timestamp = nil,
|
|
pull_timestamp = nil,
|
|
page_update_counter = nil,
|
|
last_page = nil,
|
|
periodic_push_task = nil,
|
|
periodic_push_scheduled = nil,
|
|
registration_poll_scheduled = nil,
|
|
|
|
settings = nil,
|
|
})
|
|
|
|
Bookhoard.default_settings = {
|
|
server_url = nil,
|
|
auth_token = nil,
|
|
device_id = nil,
|
|
auto_sync = false,
|
|
pages_before_update = 50,
|
|
sync_forward = SYNC_STRATEGY.PROMPT,
|
|
sync_backward = SYNC_STRATEGY.DISABLE,
|
|
sync_progress = true,
|
|
sync_bookmarks = true,
|
|
sync_highlights = true,
|
|
sync_notes = true,
|
|
sync_mode = SYNC_MODE.IMMEDIATE,
|
|
sync_endpoints = nil,
|
|
}
|
|
|
|
function Bookhoard:init()
|
|
self.push_timestamp = 0
|
|
self.pull_timestamp = 0
|
|
self.page_update_counter = 0
|
|
self.last_page = -1
|
|
self.periodic_push_scheduled = false
|
|
self.registration_poll_scheduled = false
|
|
|
|
self.periodic_push_task = function()
|
|
self.periodic_push_scheduled = false
|
|
self.page_update_counter = 0
|
|
self:updateProgress(false, false)
|
|
end
|
|
|
|
self.settings = G_reader_settings:readSetting(self.settings_key, self.default_settings)
|
|
|
|
if self.settings.auto_sync
|
|
and Device:hasSeamlessWifiToggle()
|
|
and G_reader_settings:readSetting("wifi_enable_action") ~= "turn_on" then
|
|
self.settings.auto_sync = false
|
|
logger.warn("Bookhoard: auto-sync disabled because wifi_enable_action is not turn_on")
|
|
end
|
|
|
|
self.ui.menu:registerToMainMenu(self)
|
|
end
|
|
|
|
function Bookhoard:onReaderReady()
|
|
if self.settings.auto_sync then
|
|
UIManager:nextTick(function()
|
|
self:getProgress(true, false)
|
|
end)
|
|
end
|
|
self:registerEvents()
|
|
self.last_page = self.ui:getCurrentPage()
|
|
end
|
|
|
|
function Bookhoard:registerEvents()
|
|
if self.settings.auto_sync then
|
|
self.onCloseDocument = self._onCloseDocument
|
|
self.onPageUpdate = self._onPageUpdate
|
|
self.onResume = self._onResume
|
|
self.onSuspend = self._onSuspend
|
|
self.onNetworkConnected = self._onNetworkConnected
|
|
self.onNetworkDisconnecting = self._onNetworkDisconnecting
|
|
else
|
|
self.onCloseDocument = nil
|
|
self.onPageUpdate = nil
|
|
self.onResume = nil
|
|
self.onSuspend = nil
|
|
self.onNetworkConnected = nil
|
|
self.onNetworkDisconnecting = nil
|
|
end
|
|
end
|
|
|
|
function Bookhoard:getAPI()
|
|
return BookhoardAPI:new({
|
|
server_url = self.settings.server_url,
|
|
auth_token = self.settings.auth_token,
|
|
})
|
|
end
|
|
|
|
function Bookhoard:isConfigured()
|
|
return self.settings.server_url and self.settings.auth_token
|
|
end
|
|
|
|
function Bookhoard:getSyncPeriod()
|
|
if not self.settings.auto_sync then
|
|
return _("Not available")
|
|
end
|
|
local period = self.settings.pages_before_update
|
|
if period and period > 0 then
|
|
return period
|
|
end
|
|
return _("Never")
|
|
end
|
|
|
|
function Bookhoard:addToMainMenu(menu_items)
|
|
menu_items.bookhoard_sync = {
|
|
text = _("Bookhoard sync"),
|
|
sorting_hint = "tools",
|
|
sub_item_table = self:buildMainMenu(),
|
|
}
|
|
end
|
|
|
|
function Bookhoard:buildMainMenu()
|
|
local items = {}
|
|
|
|
table.insert(items, {
|
|
text = _("Server URL"),
|
|
keep_menu_open = true,
|
|
tap_input_func = function()
|
|
return {
|
|
title = _("Bookhoard server URL"),
|
|
input = self.settings.server_url or "http://",
|
|
callback = function(input)
|
|
self.settings.server_url = input ~= "" and input or nil
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
end,
|
|
}
|
|
end,
|
|
})
|
|
|
|
if self:isConfigured() then
|
|
table.insert(items, {
|
|
text = _("Device info"),
|
|
keep_menu_open = true,
|
|
callback = function()
|
|
UIManager:show(InfoMessage:new{
|
|
text = T(_("Device ID: %1\nServer: %2"),
|
|
self.settings.device_id or _("unknown"),
|
|
self.settings.server_url),
|
|
})
|
|
end,
|
|
})
|
|
table.insert(items, {
|
|
text = _("Disconnect"),
|
|
keep_menu_open = true,
|
|
callback = function()
|
|
UIManager:show(ConfirmBox:new{
|
|
text = _("Disconnect from Bookhoard server?"),
|
|
ok_text = _("Disconnect"),
|
|
ok_callback = function()
|
|
self.settings.auth_token = nil
|
|
self.settings.device_id = nil
|
|
self.settings.sync_endpoints = nil
|
|
self.settings.auto_sync = false
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
self:registerEvents()
|
|
UIManager:askForRestart()
|
|
end,
|
|
})
|
|
end,
|
|
separator = true,
|
|
})
|
|
else
|
|
table.insert(items, {
|
|
text = _("Register device"),
|
|
keep_menu_open = true,
|
|
callback = function()
|
|
self:startRegistration()
|
|
end,
|
|
separator = true,
|
|
})
|
|
end
|
|
|
|
table.insert(items, {
|
|
text = _("Automatically keep documents in sync"),
|
|
checked_func = function() return self.settings.auto_sync end,
|
|
help_text = _([[This may lead to prompts about toggling WiFi on document close and suspend/resume, depending on your device's connectivity.]]),
|
|
callback = function()
|
|
self:toggleAutoSync()
|
|
end,
|
|
})
|
|
|
|
table.insert(items, {
|
|
text_func = function()
|
|
return T(_("Periodically sync every # pages (%1)"), self:getSyncPeriod())
|
|
end,
|
|
enabled_func = function() return self.settings.auto_sync end,
|
|
keep_menu_open = true,
|
|
callback = function(touchmenu_instance)
|
|
local spin = SpinWidget:new{
|
|
text = _([[Number of page turns between progress updates. Set to 0 to disable.]]),
|
|
value = self.settings.pages_before_update or 0,
|
|
value_min = 0,
|
|
value_max = 999,
|
|
value_step = 1,
|
|
value_hold_step = 10,
|
|
ok_text = _("Set"),
|
|
title_text = _("Pages before update"),
|
|
default_value = 50,
|
|
callback = function(spin)
|
|
self.settings.pages_before_update = spin.value > 0 and spin.value or nil
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
if touchmenu_instance then touchmenu_instance:updateItems() end
|
|
end,
|
|
}
|
|
UIManager:show(spin)
|
|
end,
|
|
})
|
|
|
|
table.insert(items, {
|
|
text_func = function()
|
|
return T(_("Sync mode (%1)"),
|
|
self.settings.sync_mode == SYNC_MODE.IMMEDIATE and _("immediate") or _("checkpoint"))
|
|
end,
|
|
sub_item_table = {
|
|
{
|
|
text = _("Immediate"),
|
|
checked_func = function()
|
|
return self.settings.sync_mode == SYNC_MODE.IMMEDIATE
|
|
end,
|
|
callback = function()
|
|
self.settings.sync_mode = SYNC_MODE.IMMEDIATE
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Checkpoint"),
|
|
checked_func = function()
|
|
return self.settings.sync_mode == SYNC_MODE.CHECKPOINT
|
|
end,
|
|
callback = function()
|
|
self.settings.sync_mode = SYNC_MODE.CHECKPOINT
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
end,
|
|
},
|
|
},
|
|
separator = true,
|
|
})
|
|
|
|
table.insert(items, {
|
|
text = _("Sync behavior"),
|
|
sub_item_table = {
|
|
{
|
|
text_func = function()
|
|
return T(_("Sync to a newer state (%1)"),
|
|
self:getStrategyName(self.settings.sync_forward))
|
|
end,
|
|
sub_item_table = {
|
|
{
|
|
text = _("Silently"),
|
|
checked_func = function()
|
|
return self.settings.sync_forward == SYNC_STRATEGY.SILENT
|
|
end,
|
|
callback = function()
|
|
self.settings.sync_forward = SYNC_STRATEGY.SILENT
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Prompt"),
|
|
checked_func = function()
|
|
return self.settings.sync_forward == SYNC_STRATEGY.PROMPT
|
|
end,
|
|
callback = function()
|
|
self.settings.sync_forward = SYNC_STRATEGY.PROMPT
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Never"),
|
|
checked_func = function()
|
|
return self.settings.sync_forward == SYNC_STRATEGY.DISABLE
|
|
end,
|
|
callback = function()
|
|
self.settings.sync_forward = SYNC_STRATEGY.DISABLE
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
text_func = function()
|
|
return T(_("Sync to an older state (%1)"),
|
|
self:getStrategyName(self.settings.sync_backward))
|
|
end,
|
|
sub_item_table = {
|
|
{
|
|
text = _("Silently"),
|
|
checked_func = function()
|
|
return self.settings.sync_backward == SYNC_STRATEGY.SILENT
|
|
end,
|
|
callback = function()
|
|
self.settings.sync_backward = SYNC_STRATEGY.SILENT
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Prompt"),
|
|
checked_func = function()
|
|
return self.settings.sync_backward == SYNC_STRATEGY.PROMPT
|
|
end,
|
|
callback = function()
|
|
self.settings.sync_backward = SYNC_STRATEGY.PROMPT
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Never"),
|
|
checked_func = function()
|
|
return self.settings.sync_backward == SYNC_STRATEGY.DISABLE
|
|
end,
|
|
callback = function()
|
|
self.settings.sync_backward = SYNC_STRATEGY.DISABLE
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
separator = true,
|
|
})
|
|
|
|
table.insert(items, {
|
|
text = _("What to sync"),
|
|
sub_item_table = {
|
|
{
|
|
text = _("Reading progress"),
|
|
checked_func = function() return self.settings.sync_progress end,
|
|
callback = function()
|
|
self.settings.sync_progress = not self.settings.sync_progress
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Bookmarks"),
|
|
checked_func = function() return self.settings.sync_bookmarks end,
|
|
callback = function()
|
|
self.settings.sync_bookmarks = not self.settings.sync_bookmarks
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Highlights"),
|
|
checked_func = function() return self.settings.sync_highlights end,
|
|
callback = function()
|
|
self.settings.sync_highlights = not self.settings.sync_highlights
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Notes"),
|
|
checked_func = function() return self.settings.sync_notes end,
|
|
callback = function()
|
|
self.settings.sync_notes = not self.settings.sync_notes
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
end,
|
|
},
|
|
},
|
|
separator = true,
|
|
})
|
|
|
|
table.insert(items, {
|
|
text = _("Sync now"),
|
|
enabled_func = function() return self:isConfigured() end,
|
|
callback = function()
|
|
self:updateProgress(true, true)
|
|
self:getProgress(true, true)
|
|
end,
|
|
})
|
|
|
|
table.insert(items, {
|
|
text = _("Push progress from this device"),
|
|
enabled_func = function() return self:isConfigured() end,
|
|
callback = function()
|
|
self:updateProgress(true, true)
|
|
end,
|
|
})
|
|
|
|
table.insert(items, {
|
|
text = _("Pull progress from server"),
|
|
enabled_func = function() return self:isConfigured() end,
|
|
callback = function()
|
|
self:getProgress(true, true)
|
|
end,
|
|
separator = true,
|
|
})
|
|
|
|
table.insert(items, {
|
|
text = _("Setup OPDS catalog"),
|
|
keep_menu_open = true,
|
|
callback = function()
|
|
if not self.settings.server_url or not self.settings.device_id then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Please configure and register your device first."),
|
|
timeout = 3,
|
|
})
|
|
return
|
|
end
|
|
local opds_url = self.settings.server_url
|
|
.. "/opds/devices/" .. self.settings.device_id .. "/catalog"
|
|
UIManager:show(InfoMessage:new{
|
|
text = T(_("Add this URL as an OPDS catalog in KOReader:\n\n%1\n\nGo to Home → + → OPDS Catalog to add it."), opds_url),
|
|
})
|
|
end,
|
|
})
|
|
|
|
return items
|
|
end
|
|
|
|
function Bookhoard:getStrategyName(strategy)
|
|
if strategy == SYNC_STRATEGY.PROMPT then
|
|
return _("Prompt")
|
|
elseif strategy == SYNC_STRATEGY.SILENT then
|
|
return _("Auto")
|
|
else
|
|
return _("Disable")
|
|
end
|
|
end
|
|
|
|
function Bookhoard:toggleAutoSync()
|
|
if not self.settings.auto_sync
|
|
and Device:hasSeamlessWifiToggle()
|
|
and G_reader_settings:readSetting("wifi_enable_action") ~= "turn_on" then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Set 'Action when Wi-Fi is off' to 'turn on' in Network settings to enable auto sync."),
|
|
})
|
|
return
|
|
end
|
|
self.settings.auto_sync = not self.settings.auto_sync
|
|
self:registerEvents()
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
|
|
if self.settings.auto_sync then
|
|
self:getProgress(true, true)
|
|
end
|
|
end
|
|
|
|
function Bookhoard:startRegistration()
|
|
if not self.settings.server_url or self.settings.server_url == "" then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Please set your server URL first."),
|
|
timeout = 3,
|
|
})
|
|
return
|
|
end
|
|
|
|
if NetworkMgr:willRerunWhenOnline(function() self:startRegistration() end) then
|
|
return
|
|
end
|
|
|
|
local device_name = Device.model or "KOReader Device"
|
|
local device_identifier = Device:info() or device_name
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Registering device…"),
|
|
timeout = 1,
|
|
})
|
|
|
|
UIManager:scheduleIn(0.5, function()
|
|
local api = BookhoardAPI:new({ server_url = self.settings.server_url })
|
|
local ok, result = api:registerDevice(device_name, device_identifier)
|
|
|
|
if not ok then
|
|
UIManager:show(InfoMessage:new{
|
|
text = T(_("Registration failed: %1"),
|
|
result and result.error or _("unknown error")),
|
|
})
|
|
return
|
|
end
|
|
|
|
self.registration_id = result.registration_id
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
text = T(_("Device registered on server.\n\nOpen your Bookhoard web UI and go to:\n%1/devices\n\nApprove this device in the \"Pending Device Registrations\" section.\n\nWaiting for approval…"), self.settings.server_url),
|
|
})
|
|
|
|
self:startRegistrationPoll()
|
|
end)
|
|
end
|
|
|
|
function Bookhoard:startRegistrationPoll()
|
|
if self.registration_poll_scheduled then return end
|
|
self.registration_poll_scheduled = true
|
|
|
|
local function poll()
|
|
self.registration_poll_scheduled = false
|
|
|
|
if not self.registration_id then return end
|
|
|
|
local api = BookhoardAPI:new({ server_url = self.settings.server_url })
|
|
local ok, result = api:checkRegistrationStatus(self.registration_id)
|
|
|
|
if not ok then
|
|
if result and result.status == 410 then
|
|
self.registration_id = nil
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Registration expired. Please try again."),
|
|
})
|
|
return
|
|
end
|
|
self.registration_poll_scheduled = true
|
|
UIManager:scheduleIn(3, poll)
|
|
return
|
|
end
|
|
|
|
if result.status == "approved" then
|
|
self.registration_id = nil
|
|
self.settings.auth_token = result.auth_token
|
|
self.settings.device_id = result.device_id and tostring(result.device_id) or nil
|
|
self.settings.sync_endpoints = result.sync_endpoints
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
self:registerEvents()
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Device registered successfully!"),
|
|
timeout = 3,
|
|
})
|
|
elseif result.status == "pending" then
|
|
self.registration_poll_scheduled = true
|
|
UIManager:scheduleIn(3, poll)
|
|
else
|
|
self.registration_id = nil
|
|
UIManager:show(InfoMessage:new{
|
|
text = T(_("Registration %1"), result.status or _("failed")),
|
|
})
|
|
end
|
|
end
|
|
|
|
UIManager:scheduleIn(3, poll)
|
|
end
|
|
|
|
function Bookhoard:getLastPercent()
|
|
if self.ui.document.info.has_pages then
|
|
return Math.roundPercent(self.ui.paging:getLastPercent())
|
|
else
|
|
return Math.roundPercent(self.ui.rolling:getLastPercent())
|
|
end
|
|
end
|
|
|
|
function Bookhoard:getLastProgress()
|
|
if self.ui.document.info.has_pages then
|
|
return self.ui.paging:getLastProgress()
|
|
else
|
|
return self.ui.rolling:getLastProgress()
|
|
end
|
|
end
|
|
|
|
function Bookhoard:getFileSHA256()
|
|
local cached = self.ui.doc_settings:readSetting("bookhoard_sha256")
|
|
if cached then return cached end
|
|
|
|
local file = io.open(self.ui.document.file, "rb")
|
|
if not file then return nil end
|
|
local data = file:read("*a")
|
|
file:close()
|
|
|
|
local hash = sha256hex(data)
|
|
if hash then
|
|
self.ui.doc_settings:saveSetting("bookhoard_sha256", hash)
|
|
end
|
|
return hash
|
|
end
|
|
|
|
function Bookhoard:getBookhoardUUID()
|
|
return self.ui.doc_settings:readSetting("bookhoard_uuid")
|
|
end
|
|
|
|
function Bookhoard:collectBookData()
|
|
local props = self.ui.doc_props
|
|
local file_path = self.ui.document.file
|
|
|
|
local title = props.display_title or ""
|
|
local authors = ""
|
|
if props.authors then
|
|
authors = props.authors
|
|
end
|
|
|
|
local file_sha256 = self:getFileSHA256()
|
|
local book_uuid = self:getBookhoardUUID()
|
|
|
|
local percentage = self:getLastPercent()
|
|
local progress = self:getLastProgress()
|
|
local page = self.ui:getCurrentPage()
|
|
local total_pages = self.ui.document:getPageCount()
|
|
|
|
local chapter = ""
|
|
if self.ui.toc and self.ui.toc.getTocTitleOfCurrentPage then
|
|
chapter = self.ui.toc:getTocTitleOfCurrentPage() or ""
|
|
end
|
|
|
|
local book_data = {
|
|
uuid = book_uuid,
|
|
sha256 = file_sha256,
|
|
title = title,
|
|
authors = authors,
|
|
percentage = percentage,
|
|
chapter = chapter,
|
|
epubcfi = progress,
|
|
page = page,
|
|
total_pages = total_pages,
|
|
file_path = file_path,
|
|
device_info = {
|
|
koreader_version = require("version"):getCurrentRevision(),
|
|
device_model = Device.model,
|
|
},
|
|
}
|
|
|
|
return book_data
|
|
end
|
|
|
|
function Bookhoard:collectAnnotations()
|
|
local bookmarks = {}
|
|
local highlights = {}
|
|
local notes = {}
|
|
|
|
if not self.ui.bookmark then
|
|
return bookmarks, highlights, notes
|
|
end
|
|
|
|
local all_bookmarks = self.ui.bookmark.bookmarks
|
|
if not all_bookmarks then
|
|
return bookmarks, highlights, notes
|
|
end
|
|
|
|
local file_sha256 = self:getFileSHA256()
|
|
local total_pages = self.ui.document:getPageCount()
|
|
|
|
for _, bm in ipairs(all_bookmarks) do
|
|
local page_num = bm.page
|
|
if type(page_num) == "string" and self.ui.document.info.has_pages == false then
|
|
page_num = self.ui.document:getPageFromXPointer(page_num)
|
|
end
|
|
page_num = tonumber(page_num) or 0
|
|
|
|
local percentage = total_pages > 0 and (page_num / total_pages) or 0
|
|
local chapter = bm.chapter or ""
|
|
|
|
local entry = {
|
|
chapter = chapter,
|
|
datetime = bm.datetime or "",
|
|
notes = bm.notes or "",
|
|
pos0 = bm.pos0 or "",
|
|
pos1 = bm.pos1 or "",
|
|
page = tostring(bm.page or ""),
|
|
text = bm.text or "",
|
|
percentage = Math.roundPercent(percentage),
|
|
book_sha256 = file_sha256,
|
|
}
|
|
|
|
local has_text = bm.text and bm.text ~= ""
|
|
local has_notes = bm.notes and bm.notes ~= ""
|
|
|
|
if has_text and has_notes then
|
|
entry.type = "note"
|
|
table.insert(notes, entry)
|
|
elseif has_text then
|
|
entry.type = "highlight"
|
|
if bm.color then
|
|
entry.color = bm.color
|
|
end
|
|
table.insert(highlights, entry)
|
|
else
|
|
entry.type = "bookmark"
|
|
table.insert(bookmarks, entry)
|
|
end
|
|
end
|
|
|
|
return bookmarks, highlights, notes
|
|
end
|
|
|
|
function Bookhoard:syncToProgress(progress, percentage)
|
|
logger.dbg("Bookhoard: sync to progress", progress, percentage)
|
|
if self.ui.document.info.has_pages then
|
|
self.ui:handleEvent(Event:new("GotoPage", tonumber(progress)))
|
|
else
|
|
self.ui:handleEvent(Event:new("GotoXPointer", progress))
|
|
end
|
|
end
|
|
|
|
function Bookhoard:updateProgress(ensure_networking, interactive)
|
|
if not self:isConfigured() then
|
|
if interactive then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Please configure and register your device first."),
|
|
timeout = 3,
|
|
})
|
|
end
|
|
return
|
|
end
|
|
|
|
if not self.settings.sync_progress then return end
|
|
|
|
local now = UIManager:getElapsedTimeSinceBoot()
|
|
if not interactive and now - self.push_timestamp <= API_CALL_DEBOUNCE_DELAY then
|
|
return
|
|
end
|
|
|
|
if ensure_networking
|
|
and NetworkMgr:willRerunWhenOnline(function() self:updateProgress(ensure_networking, interactive) end) then
|
|
return
|
|
end
|
|
|
|
UIManager:scheduleIn(0.5, function()
|
|
self:_doUpdateProgress(interactive)
|
|
end)
|
|
|
|
self.push_timestamp = now
|
|
end
|
|
|
|
function Bookhoard:_doUpdateProgress(interactive)
|
|
local book_data = self:collectBookData()
|
|
if not book_data then return end
|
|
|
|
if self.settings.sync_bookmarks or self.settings.sync_highlights or self.settings.sync_notes then
|
|
local bm, hl, nt = self:collectAnnotations()
|
|
book_data.bookmarks = self.settings.sync_bookmarks and bm or {}
|
|
book_data.highlights = self.settings.sync_highlights and hl or {}
|
|
book_data.notes = self.settings.sync_notes and nt or {}
|
|
else
|
|
book_data.bookmarks = {}
|
|
book_data.highlights = {}
|
|
book_data.notes = {}
|
|
end
|
|
|
|
local api = self:getAPI()
|
|
local ok, result = api:syncProgress(book_data, self.settings.sync_mode)
|
|
|
|
UIManager:nextTick(function()
|
|
if ok then
|
|
logger.dbg("Bookhoard: progress pushed successfully")
|
|
if result and result.device_updated and result.device_updated.uuid then
|
|
self.ui.doc_settings:saveSetting("bookhoard_uuid", result.device_updated.uuid)
|
|
self.ui.doc_settings:flush()
|
|
end
|
|
if interactive then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Progress has been pushed."),
|
|
timeout = 3,
|
|
})
|
|
end
|
|
else
|
|
logger.warn("Bookhoard: failed to push progress")
|
|
if interactive then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Failed to push progress. Check your network connection."),
|
|
timeout = 3,
|
|
})
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
function Bookhoard:getProgress(ensure_networking, interactive)
|
|
if not self:isConfigured() then
|
|
if interactive then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Please configure and register your device first."),
|
|
timeout = 3,
|
|
})
|
|
end
|
|
return
|
|
end
|
|
|
|
if not self.settings.sync_progress then return end
|
|
|
|
local now = UIManager:getElapsedTimeSinceBoot()
|
|
if not interactive and now - self.pull_timestamp <= API_CALL_DEBOUNCE_DELAY then
|
|
return
|
|
end
|
|
|
|
if ensure_networking
|
|
and NetworkMgr:willRerunWhenOnline(function() self:getProgress(ensure_networking, interactive) end) then
|
|
return
|
|
end
|
|
|
|
local book_uuid = self:getBookhoardUUID()
|
|
if not book_uuid then
|
|
if interactive then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("No Bookhoard UUID for this document. Push progress first."),
|
|
timeout = 3,
|
|
})
|
|
end
|
|
return
|
|
end
|
|
|
|
UIManager:scheduleIn(0.5, function()
|
|
self:_doGetProgress(interactive)
|
|
end)
|
|
|
|
self.pull_timestamp = now
|
|
end
|
|
|
|
function Bookhoard:_doGetProgress(interactive)
|
|
local book_uuid = self:getBookhoardUUID()
|
|
if not book_uuid then return end
|
|
|
|
local api = self:getAPI()
|
|
local ok, result = api:getMetadata(book_uuid)
|
|
|
|
UIManager:nextTick(function()
|
|
if not ok or not result then
|
|
if interactive then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Failed to pull progress."),
|
|
timeout = 3,
|
|
})
|
|
end
|
|
return
|
|
end
|
|
|
|
if not result.progress then
|
|
if interactive then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("No progress found for this document."),
|
|
timeout = 3,
|
|
})
|
|
end
|
|
return
|
|
end
|
|
|
|
local progress = result.progress
|
|
local percentage = self:getLastPercent()
|
|
local server_percentage = progress.percentage or 0
|
|
|
|
if percentage == server_percentage then
|
|
if interactive then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Progress is already synchronized."),
|
|
timeout = 3,
|
|
})
|
|
end
|
|
return
|
|
end
|
|
|
|
local self_older = server_percentage > percentage
|
|
|
|
if self_older then
|
|
if self.settings.sync_forward == SYNC_STRATEGY.SILENT then
|
|
self:syncToProgress(progress.epubcfi or progress.page, server_percentage)
|
|
self:_showSyncedMessage()
|
|
elseif self.settings.sync_forward == SYNC_STRATEGY.PROMPT then
|
|
UIManager:show(ConfirmBox:new{
|
|
text = T(_("Sync to newer location %1%% from server?"),
|
|
Math.round(server_percentage * 100)),
|
|
ok_callback = function()
|
|
self:syncToProgress(progress.epubcfi or progress.page, server_percentage)
|
|
end,
|
|
})
|
|
end
|
|
else
|
|
if self.settings.sync_backward == SYNC_STRATEGY.SILENT then
|
|
self:syncToProgress(progress.epubcfi or progress.page, server_percentage)
|
|
self:_showSyncedMessage()
|
|
elseif self.settings.sync_backward == SYNC_STRATEGY.PROMPT then
|
|
UIManager:show(ConfirmBox:new{
|
|
text = T(_("Sync to previous location %1%% from server?"),
|
|
Math.round(server_percentage * 100)),
|
|
ok_callback = function()
|
|
self:syncToProgress(progress.epubcfi or progress.page, server_percentage)
|
|
end,
|
|
})
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
function Bookhoard:_showSyncedMessage()
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Progress has been synchronized."),
|
|
timeout = 3,
|
|
})
|
|
end
|
|
|
|
function Bookhoard:_onCloseDocument()
|
|
self.onResume = nil
|
|
self.onSuspend = nil
|
|
NetworkMgr:goOnlineToRun(function()
|
|
self:updateProgress(false, false)
|
|
end)
|
|
end
|
|
|
|
function Bookhoard:schedulePeriodicPush()
|
|
UIManager:unschedule(self.periodic_push_task)
|
|
UIManager:scheduleIn(PERIODIC_PUSH_DELAY, self.periodic_push_task)
|
|
self.periodic_push_scheduled = true
|
|
end
|
|
|
|
function Bookhoard:_onPageUpdate(page)
|
|
if page == nil then return end
|
|
|
|
if self.last_page ~= page then
|
|
self.last_page = page
|
|
self.page_update_counter = self.page_update_counter + 1
|
|
if self.periodic_push_scheduled
|
|
or (self.settings.pages_before_update
|
|
and self.page_update_counter >= self.settings.pages_before_update) then
|
|
self:schedulePeriodicPush()
|
|
end
|
|
end
|
|
end
|
|
|
|
function Bookhoard:_onResume()
|
|
if Device:hasWifiRestore() and NetworkMgr.wifi_was_on
|
|
and G_reader_settings:isTrue("auto_restore_wifi") then
|
|
return
|
|
end
|
|
UIManager:scheduleIn(1, function()
|
|
self:getProgress(true, false)
|
|
end)
|
|
end
|
|
|
|
function Bookhoard:_onSuspend()
|
|
self:updateProgress(true, false)
|
|
end
|
|
|
|
function Bookhoard:_onNetworkConnected()
|
|
UIManager:scheduleIn(0.5, function()
|
|
self:getProgress(false, false)
|
|
end)
|
|
end
|
|
|
|
function Bookhoard:_onNetworkDisconnecting()
|
|
self:updateProgress(false, false)
|
|
end
|
|
|
|
function Bookhoard:onCloseWidget()
|
|
UIManager:unschedule(self.periodic_push_task)
|
|
self.periodic_push_task = nil
|
|
end
|
|
|
|
return Bookhoard
|