Merge pull request #1448 from chrox/sync_progress

add progress sync plugin and various fixes
pull/1453/head v2015.03.16-nightly
Qingping Hou 9 years ago
commit 2aebba97f2

@ -22,6 +22,6 @@ indent_size = 8
indent_style = tab
indent_size = 8
[*.{js,json,css,scss,sass,html,handlebars,tpl}]
[*.{js,css,scss,sass,html,handlebars,tpl}]
indent_style = space
indent_size = 2

@ -1 +1 @@
Subproject commit 897906d91a3cbba0eb2aa27583a6510c99ac720d
Subproject commit 7e39b96927d11083679e8329874da6ec92501d00

@ -131,22 +131,11 @@ DCREREADER_CONFIG_LINE_SPACE_PERCENT_LARGE = 120
DCREREADER_PROGRESS_BAR = 1
-- configure "mini" progress bar
DMINIBAR_ALL_AT_ONCE = false
DMINIBAR_PROGRESSBAR = true
DMINIBAR_TIME = true
DMINIBAR_PAGES = true
DMINIBAR_NEXT_CHAPTER = true
DMINIBAR_BATTERY = true
DMINIBAR_PROGRESS_MARKER = true -- Black notch for each TOC entry
DMINIBAR_TOC_MARKER_WIDTH = 2 -- Looses usefulness > 3
DMINIBAR_HEIGHT = 7 -- Should be smaller than DMINIBAR_CONTAINER_HEIGHT
DMINIBAR_CONTAINER_HEIGHT = 14 -- Larger means more padding at the bottom, at the risk of eating into the last line
DMINIBAR_FONT_SIZE = 14
-- gesture detector defaults
DGESDETECT_DISABLE_DOUBLE_TAP = true

@ -75,52 +75,65 @@ function FileManager:init()
function file_chooser:onFileHold(file)
--DEBUG("hold file", file)
self.file_dialog = ButtonDialog:new{
buttons = {
local buttons = {
{
{
text = _("Copy"),
callback = function()
copyFile(file)
UIManager:close(self.file_dialog)
end,
},
{
text = _("Paste"),
enabled = fileManager.clipboard and true or false,
callback = function()
pasteHere(file)
self:refreshPath()
UIManager:close(self.file_dialog)
end,
},
},
{
{
{
text = _("Copy"),
callback = function()
copyFile(file)
UIManager:close(self.file_dialog)
end,
},
{
text = _("Paste"),
enabled = fileManager.clipboard and true or false,
callback = function()
pasteHere(file)
self:refreshPath()
UIManager:close(self.file_dialog)
end,
},
text = _("Cut"),
callback = function()
cutFile(file)
UIManager:close(self.file_dialog)
end,
},
{
{
text = _("Cut"),
callback = function()
cutFile(file)
UIManager:close(self.file_dialog)
end,
},
{
text = _("Delete"),
callback = function()
local path = util.realpath(file)
local ConfirmBox = require("ui/widget/confirmbox")
UIManager:close(self.file_dialog)
UIManager:show(ConfirmBox:new{
text = _("Are you sure that you want to delete this file?\n") .. file .. ("\n") .. _("If you delete a file, it is permanently lost."),
ok_callback = function()
deleteFile(file)
self:refreshPath()
end,
})
end,
},
text = _("Delete"),
callback = function()
local path = util.realpath(file)
local ConfirmBox = require("ui/widget/confirmbox")
UIManager:close(self.file_dialog)
UIManager:show(ConfirmBox:new{
text = _("Are you sure that you want to delete this file?\n") .. file .. ("\n") .. _("If you delete a file, it is permanently lost."),
ok_callback = function()
deleteFile(file)
self:refreshPath()
end,
})
end,
},
},
}
if lfs.attributes(file, "mode") == "directory" then
local realpath = util.realpath(file)
table.insert(buttons, {
{
text = _("Set as HOME directory"),
callback = function()
G_reader_settings:saveSetting("home_dir", realpath)
UIManager:close(self.file_dialog)
end
}
})
end
self.file_dialog = ButtonDialog:new{
buttons = buttons,
}
UIManager:show(self.file_dialog)
return true
end

@ -297,6 +297,23 @@ function ReaderBookmark:addBookmark(item)
table.insert(self.bookmarks, _middle + direction, item)
end
-- binary search of sorted bookmarks
function ReaderBookmark:isBookmarkAdded(item)
local _start, _middle, _end, direction = 1, 1, #self.bookmarks, 0
while _start <= _end do
local v = self.bookmarks[_middle]
_middle = math.floor((_start + _end)/2)
if self:isBookmarkSame(item, self.bookmarks[_middle]) then
return true
end
if self:isBookmarkInPageOrder(item, self.bookmarks[_middle]) then
_end, direction = _middle - 1, 0
else
_start, direction = _middle + 1, 1
end
end
end
-- binary search to remove bookmark
function ReaderBookmark:removeBookmark(item)
local _start, _middle, _end = 1, 1, #self.bookmarks

@ -7,6 +7,7 @@ local ProgressWidget = require("ui/widget/progresswidget")
local HorizontalGroup = require("ui/widget/horizontalgroup")
local TextWidget = require("ui/widget/textwidget")
local GestureRange = require("ui/gesturerange")
local Blitbuffer = require("ffi/blitbuffer")
local UIManager = require("ui/uimanager")
local Device = require("device")
local Screen = require("device").screen
@ -14,7 +15,7 @@ local Geom = require("ui/geometry")
local Event = require("ui/event")
local Font = require("ui/font")
local DEBUG = require("dbg")
local Blitbuffer = require("ffi/blitbuffer")
local _ = require("gettext")
local ReaderFooter = InputContainer:new{
mode = 1,
@ -30,27 +31,42 @@ local ReaderFooter = InputContainer:new{
bar_height = Screen:scaleBySize(DMINIBAR_HEIGHT),
height = Screen:scaleBySize(DMINIBAR_CONTAINER_HEIGHT),
padding = Screen:scaleBySize(10),
settings = {},
}
function ReaderFooter:init()
self.pageno = self.view.state.page
self.pages = self.view.document:getPageCount()
self.settings = G_reader_settings:readSetting("footer") or {
disabled = false,
all_at_once = false,
progress_bar = true,
toc_markers = true,
battery = true,
time = true,
page_progress = true,
pages_left = true,
percentage = true,
}
local text_default = ""
if DMINIBAR_ALL_AT_ONCE then
if self.settings.all_at_once then
local info = {}
if DMINIBAR_BATTERY then
if self.settings.battery then
table.insert(info, "B:100%")
end
if DMINIBAR_TIME then
if self.settings.time then
table.insert(info, "WW:WW")
end
if DMINIBAR_PAGES then
if self.settings.page_progress then
table.insert(info, "0000 / 0000")
end
if DMINIBAR_NEXT_CHAPTER then
if self.settings.pages_left then
table.insert(info, "=> 000")
end
if self.settings.percentage then
table.insert(info, "R:100%")
end
text_default = table.concat(info, " | ")
else
text_default = string.format(" %d / %d ", self.pages, self.pages)
@ -62,7 +78,7 @@ function ReaderFooter:init()
}
local text_width = self.progress_text:getSize().w
local ticks_candidates = {}
if self.ui.toc and DMINIBAR_PROGRESS_MARKER then
if self.ui.toc and self.settings.toc_markers then
local max_level = self.ui.toc:getMaxDepth()
for i = 0, -max_level, -1 do
local ticks = self.ui.toc:getTocTicks(i)
@ -90,7 +106,7 @@ function ReaderFooter:init()
dimen = Geom:new{ w = text_width, h = self.height },
self.progress_text,
}
if DMINIBAR_PROGRESSBAR then
if self.settings.progress_bar then
table.insert(horizontal_group, bar_container)
end
table.insert(horizontal_group, text_container)
@ -134,6 +150,50 @@ function ReaderFooter:init()
self:applyFooterMode()
end
local options = {
all_at_once = _("Show all at once"),
progress_bar = _("Progress bar"),
toc_markers = _("Chapter markers"),
battery = _("Battery status"),
time = _("Current time"),
page_progress = ("Current page"),
pages_left = ("Pages left in this chapter"),
percentage = ("Progress percentage"),
}
function ReaderFooter:addToMainMenu(tab_item_table)
local get_minibar_option = function(option)
return {
text = options[option],
checked_func = function()
return self.settings[option] == true
end,
enabled_func = function()
return not self.settings.diabled
end,
callback = function()
self.settings[option] = not self.settings[option]
G_reader_settings:saveSetting("footer", self.settings)
self:init()
UIManager:setDirty("all", "partial")
end,
}
end
table.insert(tab_item_table.setting, {
text = _("Status bar"),
sub_item_table = {
get_minibar_option("all_at_once"),
get_minibar_option("progress_bar"),
get_minibar_option("toc_markers"),
get_minibar_option("battery"),
get_minibar_option("time"),
get_minibar_option("page_progress"),
get_minibar_option("pages_left"),
get_minibar_option("percentage"),
}
})
end
function ReaderFooter:getBatteryInfo()
local powerd = Device:getPowerDevice()
--local state = powerd:isCharging() and -1 or powerd:getCapacity()
@ -153,23 +213,30 @@ function ReaderFooter:getNextChapterInfo()
return "=> " .. (left and left or self.pages - self.pageno)
end
function ReaderFooter:getProgressPercentage()
return string.format("R:%1.f%%", self.progress_bar.percentage * 100)
end
function ReaderFooter:updateFooterPage()
if type(self.pageno) ~= "number" then return end
self.progress_bar.percentage = self.pageno / self.pages
if DMINIBAR_ALL_AT_ONCE then
if self.settings.all_at_once then
local info = {}
if DMINIBAR_BATTERY then
if self.settings.battery then
table.insert(info, self:getBatteryInfo())
end
if DMINIBAR_TIME then
if self.settings.time then
table.insert(info, self:getTimeInfo())
end
if DMINIBAR_PAGES then
if self.settings.page_progress then
table.insert(info, self:getProgressInfo())
end
if DMINIBAR_NEXT_CHAPTER then
if self.settings.pages_left then
table.insert(info, self:getNextChapterInfo())
end
if self.settings.percentage then
table.insert(info, self:getProgressPercentage())
end
self.progress_text.text = table.concat(info, " | ")
else
local info = ""
@ -181,10 +248,11 @@ function ReaderFooter:updateFooterPage()
info = self:getNextChapterInfo()
elseif self.mode == 4 then
info = self:getBatteryInfo()
elseif self.mode == 5 then
info = self:getProgressPercentage()
end
self.progress_text.text = info
end
end
function ReaderFooter:updateFooterPos()
@ -223,6 +291,7 @@ function ReaderFooter:applyFooterMode(mode)
-- 2 for footer time info
-- 3 for footer next_chapter info
-- 4 for battery status
-- 5 for progress percentage
if mode ~= nil then self.mode = mode end
if self.mode == 0 then
self.view.footer_visible = false
@ -250,20 +319,23 @@ function ReaderFooter:onTapFooter(arg, ges)
self.ui:handleEvent(Event:new("GotoPercentage", percentage))
end
else
self.mode = (self.mode + 1) % 5
if DMINIBAR_ALL_AT_ONCE and (self.mode > 1) then
self.mode = (self.mode + 1) % 6
if self.settings.all_at_once and (self.mode > 1) then
self.mode = 0
end
if (self.mode == 1) and not DMINIBAR_PAGES then
if (self.mode == 1) and not self.settings.page_progress then
self.mode = 2
end
if (self.mode == 2) and not DMINIBAR_TIME then
if (self.mode == 2) and not self.settings.time then
self.mode = 3
end
if (self.mode == 3) and not DMINIBAR_NEXT_CHAPTER then
if (self.mode == 3) and not self.settings.pages_left then
self.mode = 4
end
if (self.mode == 4) and not DMINIBAR_BATTERY then
if (self.mode == 4) and not self.settings.battery then
self.mode = 5
end
if (self.mode == 5) and not self.settings.percentage then
self.mode = 0
end
self:applyFooterMode()

@ -31,7 +31,7 @@ function ReaderGoto:onShowGotoDialog()
title = self.goto_dialog_title,
input_hint = "(1 - "..self.document:getPageCount()..")",
buttons = {
{
{
{
text = _("Cancel"),
enabled = true,
@ -50,15 +50,13 @@ function ReaderGoto:onShowGotoDialog()
text = _("Location"),
enabled = not self.document.info.has_pages,
callback = function()
self:gotoLocation()
self:gotoPage()
end,
},
},
},
input_type = "number",
enter_callback = self.document.info.has_pages
and function() self:gotoPage() end
or function() self:gotoLocation() end,
enter_callback = function() self:gotoPage() end,
width = Screen:getWidth() * 0.8,
height = Screen:getHeight() * 0.2,
}
@ -72,21 +70,17 @@ function ReaderGoto:close()
end
function ReaderGoto:gotoPage()
local number = tonumber(self.goto_dialog:getInputText())
if number then
self.ui:handleEvent(Event:new("GotoPage", number))
end
self:close()
return true
end
function ReaderGoto:gotoLocation()
local number = tonumber(self.goto_dialog:getInputText())
local page_number = self.goto_dialog:getInputText()
local relative_sign = page_number:sub(1, 1)
local number = tonumber(page_number)
if number then
self.ui:handleEvent(Event:new("GotoPage", number))
if relative_sign == "+" or relative_sign == "-" then
self.ui:handleEvent(Event:new("GotoRelativePage", number))
else
self.ui:handleEvent(Event:new("GotoPage", number))
end
self:close()
end
self:close()
return true
end
return ReaderGoto

@ -385,10 +385,28 @@ function ReaderHighlight:highlightFromHoldPos()
end
function ReaderHighlight:onHighlight()
self:highlightFromHoldPos()
self:saveHighlight()
end
function ReaderHighlight:getHighlightBookmarkItem()
if self.hold_pos and not self.selected_text then
self:highlightFromHoldPos()
end
if self.selected_text and self.selected_text.pos0 and self.selected_text.pos1 then
local datetime = os.date("%Y-%m-%d %H:%M:%S")
local page = self.ui.document.info.has_pages and
self.hold_pos.page or self.selected_text.pos0
return {
page = page,
pos0 = self.selected_text.pos0,
pos1 = self.selected_text.pos1,
datetime = datetime,
notes = self.selected_text.text,
highlighted = true,
}
end
end
function ReaderHighlight:saveHighlight()
DEBUG("save highlight")
local page = self.hold_pos.page
@ -407,14 +425,10 @@ function ReaderHighlight:saveHighlight()
drawer = self.view.highlight.saved_drawer,
}
table.insert(self.view.highlight.saved[page], hl_item)
self.ui.bookmark:addBookmark({
page = self.ui.document.info.has_pages and page or self.selected_text.pos0,
pos0 = self.selected_text.pos0,
pos1 = self.selected_text.pos1,
datetime = datetime,
notes = self.selected_text.text,
highlighted = true,
})
local bookmark_item = self:getHighlightBookmarkItem()
if bookmark_item then
self.ui.bookmark:addBookmark(bookmark_item)
end
--[[
-- disable exporting hightlights to My Clippings
-- since it's not portable and there is a better Evernote plugin
@ -468,7 +482,8 @@ function ReaderHighlight:onHighlightSearch()
DEBUG("search highlight")
self:highlightFromHoldPos()
if self.selected_text then
self.ui:handleEvent(Event:new("ShowSearchDialog", self.selected_text.text))
local text = require("util").stripePunctuations(self.selected_text.text)
self.ui:handleEvent(Event:new("ShowSearchDialog", text))
end
end

@ -126,13 +126,25 @@ function ReaderPaging:onReadSettings(config)
if self.show_overlap_enable == nil then
self.show_overlap_enable = DSHOWOVERLAP
end
self.flipping_zoom_mode = config:readSetting("flipping_zoom_mode") or "page"
end
function ReaderPaging:onSaveSettings()
self.ui.doc_settings:saveSetting("page_positions", self.page_positions)
self.ui.doc_settings:saveSetting("last_page", self:getTopPage())
self.ui.doc_settings:saveSetting("percent_finished", self.current_page/self.number_of_pages)
self.ui.doc_settings:saveSetting("percent_finished", self:getLastPercent())
self.ui.doc_settings:saveSetting("show_overlap_enable", self.show_overlap_enable)
self.ui.doc_settings:saveSetting("flipping_zoom_mode", self.flipping_zoom_mode)
end
function ReaderPaging:getLastProgress()
return self:getTopPage()
end
function ReaderPaging:getLastPercent()
if self.current_page > 0 and self.number_of_pages > 0 then
return self.current_page/self.number_of_pages
end
end
function ReaderPaging:addToMainMenu(tab_item_table)
@ -249,7 +261,7 @@ function ReaderPaging:enterFlippingMode()
self.view.page_scroll = false
Input.disable_double_tap = false
DGESDETECT_DISABLE_DOUBLE_TAP = false
self.ui:handleEvent(Event:new("SetZoomMode", "page"))
self.ui:handleEvent(Event:new("SetZoomMode", self.flipping_zoom_mode))
end
function ReaderPaging:exitFlippingMode()
@ -258,6 +270,7 @@ function ReaderPaging:exitFlippingMode()
self.view.page_scroll = self.orig_scroll_mode
DGESDETECT_DISABLE_DOUBLE_TAP = self.DGESDETECT_DISABLE_DOUBLE_TAP
Input.disable_double_tap = DGESDETECT_DISABLE_DOUBLE_TAP
self.flipping_zoom_mode = self.view.zoom_mode
DEBUG("restore zoom mode", self.orig_zoom_mode)
self.ui:handleEvent(Event:new("SetZoomMode", self.orig_zoom_mode))
end
@ -786,6 +799,11 @@ function ReaderPaging:onGotoPage(number)
return true
end
function ReaderPaging:onGotoRelativePage(number)
self:gotoPage(self.current_page + number)
return true
end
function ReaderPaging:onGotoPercentage(percentage)
if percentage < 0 then percentage = 0 end
if percentage > 1 then percentage = 1 end

@ -208,6 +208,10 @@ function ReaderRolling:onSaveSettings()
self.ui.doc_settings:saveSetting("show_overlap_enable", self.show_overlap_enable)
end
function ReaderRolling:getLastProgress()
return self.xpointer
end
function ReaderRolling:addToMainMenu(tab_item_table)
-- FIXME: repeated code with page overlap menu for readerpaging
-- needs to keep only one copy of the logic as for the DRY principle.
@ -327,6 +331,14 @@ function ReaderRolling:onGotoPage(number)
return true
end
function ReaderRolling:onGotoRelativePage(number)
if number then
self:gotoPage(self.current_page + number)
end
self.xpointer = self.ui.document:getXPointer()
return true
end
function ReaderRolling:onGotoXPointer(xp)
self:gotoXPointer(xp)
self.xpointer = xp

@ -29,7 +29,6 @@ function ReaderSearch:addToMainMenu(tab_item_table)
end
function ReaderSearch:onShowSearchDialog(text)
text = require("util").stripePunctuations(text)
local do_search = function(search_func, text, param)
return function()
local res = search_func(self, text, param)

@ -73,14 +73,17 @@ function ReaderView:init()
self.state.page = nil
-- fix inherited dim_area for following opened documents
self:resetDimArea()
self:resetLayout()
self:addWidgets()
self.ui:registerPostInitCallback(function()
self.ui.menu:registerToMainMenu(self.footer)
end)
end
function ReaderView:resetDimArea()
self.dim_area = Geom:new{w = 0, h = 0}
end
function ReaderView:resetLayout()
function ReaderView:addWidgets()
self.dogear = ReaderDogear:new{
view = self,
ui = self.ui,
@ -105,6 +108,12 @@ function ReaderView:resetLayout()
self[3] = self.flipping
end
function ReaderView:resetLayout()
for i, widget in ipairs(self) do
widget:init()
end
end
function ReaderView:paintTo(bb, x, y)
DEBUG("painting", self.visible_area, "to", x, y)
if self.page_scroll then

@ -368,7 +368,8 @@ function ReaderUI:closeDocument()
self.document = nil
end
function ReaderUI:onCloseDocument()
function ReaderUI:notifyCloseDocument()
self:handleEvent(Event:new("CloseDocument"))
if self.document:isEdited() then
UIManager:show(ConfirmBox:new{
text = _("Do you want to save this document?"),
@ -392,7 +393,7 @@ function ReaderUI:onClose()
self:saveSettings()
if self.document ~= nil then
DEBUG("closing document")
self:onCloseDocument()
self:notifyCloseDocument()
end
UIManager:close(self.dialog, "full")
-- serialize last used items for later launch

@ -97,13 +97,15 @@ function Device:onPowerEvent(ev)
local Screensaver = require("ui/screensaver")
if (ev == "Power" or ev == "Suspend") and not self.screen_saver_mode then
local UIManager = require("ui/uimanager")
-- flushing settings first in case the screensaver takes too long time that
-- flushing has no chance to run
UIManager:sendEvent(Event:new("FlushSettings"))
DEBUG("Suspending...")
-- always suspend in portrait mode
self.orig_rotation_mode = self.screen:getRotationMode()
self.screen:setRotationMode(0)
Screensaver:show()
self:prepareSuspend()
UIManager:sendEvent(Event:new("FlushSettings"))
UIManager:scheduleIn(10, self.suspend)
elseif (ev == "Power" or ev == "Resume") and self.screen_saver_mode then
DEBUG("Resuming...")

@ -123,6 +123,7 @@ end
function DjvuDocument:register(registry)
registry:addProvider("djvu", "application/djvu", self)
registry:addProvider("djv", "application/djvu", self)
end
return DjvuDocument

@ -2,7 +2,6 @@ local UIManager = require("ui/uimanager")
local DEBUG = require("dbg")
local HTTPClient = {
headers = {},
input_timeouts = 0,
INPUT_TIMEOUT = 100*1000,
}
@ -14,20 +13,7 @@ function HTTPClient:new()
return o
end
function HTTPClient:addHeader(header, value)
self.headers[header] = value
end
function HTTPClient:removeHeader(header)
self.headers[header] = nil
end
function HTTPClient:request(request, response_callback, error_callback)
request.on_headers = function(headers)
for header, value in pairs(self.headers) do
headers[header] = value
end
end
function HTTPClient:request(request, response_callback)
request.connect_timeout = 10
request.request_timeout = 20
UIManager:initLooper()
@ -36,15 +22,15 @@ function HTTPClient:request(request, response_callback, error_callback)
UIManager.INPUT_TIMEOUT = self.INPUT_TIMEOUT
self.input_timeouts = self.input_timeouts + 1
local turbo = require("turbo")
-- disable success and warning logs
turbo.log.categories.success = false
local res = coroutine.yield(
turbo.async.HTTPClient():fetch(request.url, request))
turbo.log.categories.warning = false
local client = turbo.async.HTTPClient({verify_ca = "none"})
local res = coroutine.yield(client:fetch(request.url, request))
-- reset INPUT_TIMEOUT to nil when all HTTP requests are fullfilled.
self.input_timeouts = self.input_timeouts - 1
UIManager.INPUT_TIMEOUT = self.input_timeouts > 0 and self.INPUT_TIMEOUT or nil
if res.error and error_callback then
error_callback(res)
elseif response_callback then
if response_callback then
response_callback(res)
end
end)

@ -62,7 +62,7 @@ local KoptOptions = {
name = "page_margin",
name_text = S.PAGE_MARGIN,
toggle = {S.SMALL, S.MEDIUM, S.LARGE},
values = {0.05, 0.10, 0.15},
values = {0.05, 0.10, 0.25},
default_value = DKOPTREADER_CONFIG_PAGE_MARGIN,
event = "MarginUpdate",
},

@ -158,9 +158,11 @@ function DictQuickLookup:update()
end,
},
{
text = _("Highlight"),
text = self:getHighlightText(),
enabled = select(2, self:getHighlightText()),
callback = function()
self.ui:handleEvent(Event:new("Highlight"))
self:update()
end,
},
{
@ -279,6 +281,21 @@ function DictQuickLookup:onShow()
return true
end
function DictQuickLookup:getHighlightedItem()
return self.ui.highlight:getHighlightBookmarkItem()
end
function DictQuickLookup:getHighlightText()
local item = self:getHighlightedItem()
if not item then
return _("Highlight"), false
elseif self.ui.bookmark:isBookmarkAdded(item) then
return _("Unhighlight"), false
else
return _("Highlight"), true
end
end
function DictQuickLookup:isPrevDictAvaiable()
return self.dict_index > 1
end

@ -546,7 +546,8 @@ function TouchMenu:onMenuSelect(item)
sub_item_table = item.sub_item_table_func()
end
if sub_item_table == nil then
local callback = item.callback
-- keep menu opened if this item is a check option
local callback, refresh = item.callback, item.checked or item.checked_func
if item.callback_func then
callback = item.callback_func()
end
@ -554,8 +555,12 @@ function TouchMenu:onMenuSelect(item)
-- put stuff in scheduler so we can see
-- the effect of inverted menu item
UIManager:scheduleIn(0.1, function()
self:closeMenu()
callback()
if refresh then
self:updateItems()
else
self:closeMenu()
end
end)
end
else

@ -1 +1 @@
Subproject commit 576b42e8aedd29af20415fd840f26623258caaa3
Subproject commit 64caed00259bb23b789f2a8051e77efa9bcde9cc

@ -0,0 +1,152 @@
local UIManager = require("ui/uimanager")
local DEBUG = require("dbg")
local KOSyncClient = {
service_spec = nil,
}
function KOSyncClient:new(o)
local o = o or {}
setmetatable(o, self)
self.__index = self
if o.init then o:init() end
return o
end
function KOSyncClient:init()
local Spore = require("Spore")
self.client = Spore.new_from_spec(self.service_spec)
package.loaded['Spore.Middleware.GinClient'] = {}
require('Spore.Middleware.GinClient').call = function(self, req)
req.headers['accept'] = "application/vnd.koreader.v1+json"
end
package.loaded['Spore.Middleware.KOSyncAuth'] = {}
require('Spore.Middleware.KOSyncAuth').call = function(args, req)
req.headers['x-auth-user'] = args.username
req.headers['x-auth-key'] = args.userkey
end
local HTTPClient = require("httpclient")
local async_http_client = HTTPClient:new()
package.loaded['Spore.Middleware.AsyncHTTP'] = {}
require('Spore.Middleware.AsyncHTTP').call = function(args, req)
req:finalize()
local result
async_http_client:request({
url = req.url,
method = req.method,
body = req.env.spore.payload,
on_headers = function(headers)
for header, value in pairs(req.headers) do
if type(header) == 'string' then
headers:add(header, value)
end
end
end,
}, function(res)
result = res
-- Turbo HTTP client uses code instead of status
-- change to status so that Spore can understand
result.status = res.code
-- fallback to sync http request
if result.error then result = nil end
coroutine.resume(args.thread)
end)
return coroutine.create(function() coroutine.yield(result) end)
end
end
function KOSyncClient:register(username, password)
self.client:reset_middlewares()
self.client:enable('Format.JSON')
self.client:enable("GinClient")
local ok, res = pcall(function()
return self.client:register({
username = username,
password = password,
})
end)
if ok then
return res.status == 201, res.body
else
DEBUG(ok, res)
return false, res.body
end
end
function KOSyncClient:authorize(username, password)
self.client:reset_middlewares()
self.client:enable('Format.JSON')
self.client:enable("GinClient")
self.client:enable("KOSyncAuth", {
username = username,
userkey = password,
})
local ok, res = pcall(function()
return self.client:authorize()
end)
if ok then
return res.status == 200, res.body
else
DEBUG("err:", res)
return false, res
end
end
function KOSyncClient:update_progress(username, password,
document, progress, percentage, device, callback)
self.client:reset_middlewares()
self.client:enable('Format.JSON')
self.client:enable("GinClient")
self.client:enable("KOSyncAuth", {
username = username,
userkey = password,
})
local co = coroutine.create(function()
local ok, res = pcall(function()
return self.client:update_progress({
document = document,
progress = progress,
percentage = percentage,
device = device,
})
end)
if ok then
callback(res.status == 200, res.body)
else
DEBUG("err:", res)
callback(false, res)
end
end)
self.client:enable("AsyncHTTP", {thread = co})
coroutine.resume(co)
UIManager.INPUT_TIMEOUT = 100
end
function KOSyncClient:get_progress(username, password,
document, callback)
self.client:reset_middlewares()
self.client:enable('Format.JSON')
self.client:enable("GinClient")
self.client:enable("KOSyncAuth", {
username = username,
userkey = password,
})
local co = coroutine.create(function()
local ok, res = pcall(function()
return self.client:get_progress({
document = document,
})
end)
if ok then
callback(res.status == 200, res.body)
else
DEBUG("err:", res)
callback(false, res)
end
end)
self.client:enable("AsyncHTTP", {thread = co})
coroutine.resume(co)
UIManager.INPUT_TIMEOUT = 100
end
return KOSyncClient

@ -0,0 +1,49 @@
{
"base_url" : "https://vislab.bjmu.edu.cn:7200/",
"name" : "koreader-sync-api",
"methods" : {
"register" : {
"path" : "/users/create",
"method" : "POST",
"required_params" : [
"username",
"password",
],
"payload" : [
"username",
"password",
],
"expected_status" : [201, 402]
},
"authorize" : {
"path" : "/users/auth",
"method" : "GET",
"expected_status" : [200, 401]
},
"update_progress" : {
"path" : "/syncs/progress",
"method" : "PUT",
"required_params" : [
"document",
"progress",
"percentage",
"device",
],
"payload" : [
"document",
"progress",
"percentage",
"device",
],
"expected_status" : [200, 202, 401]
},
"get_progress" : {
"path" : "/syncs/progress/:document",
"method" : "GET",
"required_params" : [
"document",
],
"expected_status" : [200, 401]
},
}
}

@ -0,0 +1,307 @@
local InputContainer = require("ui/widget/container/inputcontainer")
local LoginDialog = require("ui/widget/logindialog")
local InfoMessage = require("ui/widget/infomessage")
local ConfirmBox = require("ui/widget/confirmbox")
local DocSettings = require("docsettings")
local NetworkMgr = require("ui/networkmgr")
local UIManager = require("ui/uimanager")
local Screen = require("device").screen
local Device = require("device")
local Event = require("ui/event")
local Math = require("optmath")
local DEBUG = require("dbg")
local T = require("ffi/util").template
local _ = require("gettext")
local md5 = require("MD5")
local l10n = {
_("Unknown server error."),
_("Unauthorized"),
_("Username is already registered."),
}
local KOSync = InputContainer:new{
name = "kosync",
title = _("Register/login to Koreader server"),
}
function KOSync:init()
local settings = G_reader_settings:readSetting("kosync") or {}
self.kosync_username = settings.username
self.kosync_userkey = settings.userkey
self.kosync_auto_sync = not (settings.auto_sync == false)
self.ui:registerPostInitCallback(function()
if self.kosync_auto_sync then
UIManager:scheduleIn(1, function() self:getProgress() end)
end
end)
self.ui.menu:registerToMainMenu(self)
end
function KOSync:addToMainMenu(tab_item_table)
table.insert(tab_item_table.plugins, {
text = _("Progress sync"),
sub_item_table = {
{
text_func = function()
return self.kosync_userkey and (_("Logout"))
or _("Register") .. " / " .. _("Login")
end,
callback_func = function()
return self.kosync_userkey and
function() self:logout() end or
function() self:login() end
end,
},
{
text = _("Auto sync"),
checked_func = function() return self.kosync_auto_sync end,
callback = function()
self.kosync_auto_sync = not self.kosync_auto_sync
end,
},
{
text = _("Sync now"),
enabled_func = function()
return self.kosync_userkey ~= nil
end,
callback = function()
self:updateProgress()
self:getProgress(true)
end,
}
}
})
end
function KOSync:login()
if NetworkMgr:getWifiStatus() == false then
NetworkMgr:promptWifiOn()
end
self.login_dialog = LoginDialog:new{
title = self.title,
username = self.kosync_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,
},
{
text = _("Register"),
enabled = true,
callback = function()
local username, password = self:getCredential()
self:closeDialog()
UIManager:scheduleIn(0.5, function()
self:doRegister(username, password)
end)
UIManager:show(InfoMessage:new{
text = _("Registering. 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 KOSync:closeDialog()
self.login_dialog:onClose()
UIManager:close(self.login_dialog)
end
function KOSync:getCredential()
return self.login_dialog:getCredential()
end
function KOSync:doRegister(username, password)
local KOSyncClient = require("KOSyncClient")
local client = KOSyncClient:new{
service_spec = self.path .. "/api.json"
}
local userkey = md5:sum(password)
local ok, status, body = pcall(client.register, client, username, userkey)
if not ok and status then
UIManager:show(InfoMessage:new{
text = _("An error occurred while registering:") ..
"\n" .. status,
})
elseif ok then
if status then
self.kosync_username = username
self.kosync_userkey = userkey
UIManager:show(InfoMessage:new{
text = _("Registered to Koreader server successfully."),
})
else
UIManager:show(InfoMessage:new{
text = _(body.message or "Unknown server error"),
})
end
end
self:onSaveSettings()
end
function KOSync:doLogin(username, password)
local KOSyncClient = require("KOSyncClient")
local client = KOSyncClient:new{
service_spec = self.path .. "/api.json"
}
local userkey = md5:sum(password)
local ok, status, body = pcall(client.authorize, client, username, userkey)
if not ok and status then
UIManager:show(InfoMessage:new{
text = _("An error occurred while logging in:") ..
"\n" .. status,
})
elseif ok then
if status then
self.kosync_username = username
self.kosync_userkey = userkey
UIManager:show(InfoMessage:new{
text = _("Logged in to Koreader server successfully."),
})
else
UIManager:show(InfoMessage:new{
text = _(body.message or "Unknown server error"),
})
end
end
self:onSaveSettings()
end
function KOSync:logout()
self.kosync_userkey = nil
self.kosync_auto_sync = true
self:onSaveSettings()
end
function KOSync:getLastPercent()
if self.ui.document.info.has_pages then
return self.ui.paging:getLastPercent()
else
return self.ui.rolling:getLastPercent()
end
end
function KOSync:getLastProgress()
if self.ui.document.info.has_pages then
return self.ui.paging:getLastProgress()
else
return self.ui.rolling:getLastProgress()
end
end
function KOSync:syncToProgress(progress)
DEBUG("sync to", progress)
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 KOSync:updateProgress()
if self.kosync_username and self.kosync_userkey then
local KOSyncClient = require("KOSyncClient")
local client = KOSyncClient:new{
service_spec = self.path .. "/api.json"
}
local doc_digest = self.view.document:fastDigest()
local progress = self:getLastProgress()
local percentage = self:getLastPercent()
local ok, err = pcall(client.update_progress, client,
self.kosync_username, self.kosync_userkey,
doc_digest, progress, percentage, Device.model,
function(ok, body)
DEBUG("update progress for", self.view.document.file, ok)
end)
if not ok and err then
DEBUG("err:", err)
end
end
end
function KOSync:getProgress(manual)
if self.kosync_username and self.kosync_userkey then
local KOSyncClient = require("KOSyncClient")
local client = KOSyncClient:new{
service_spec = self.path .. "/api.json"
}
local doc_digest = self.view.document:fastDigest()
local ok, err = pcall(client.get_progress, client,
self.kosync_username, self.kosync_userkey,
doc_digest, function(ok, body)
DEBUG("get progress for", self.view.document.file, ok, body)
if body and body.percentage then
local progress = self:getLastProgress()
local percentage = self:getLastPercent()
DEBUG("current progress", percentage)
if body.percentage > percentage and body.progress ~= progress then
UIManager:show(ConfirmBox:new{
text = T(_("Sync to furthest location %1% from device '%2'?"),
Math.round(body.percentage*100), body.device),
ok_callback = function()
self:syncToProgress(body.progress)
end,
})
elseif manual and body.progress == progress then
UIManager:show(InfoMessage:new{
text = _("We are already synchronized."),
timeout = 3,
})
end
end
end)
if not ok and err then
DEBUG("err:", err)
end
end
end
function KOSync:onSaveSettings()
local settings = {
username = self.kosync_username,
userkey = self.kosync_userkey,
auto_sync = self.kosync_auto_sync,
}
G_reader_settings:saveSetting("kosync", settings)
end
function KOSync:onCloseDocument()
DEBUG("on close document")
if self.kosync_auto_sync then
self:updateProgress()
end
end
return KOSync

@ -5,8 +5,8 @@ require "defaults"
pcall(dofile, "defaults.persistent.lua")
-- set search path for 'require()'
package.path = "common/?.lua;frontend/?.lua;" .. package.path
package.cpath = "common/?.so;common/?.dll;/usr/lib/lua/?.so;" .. package.cpath
package.path = "common/?.lua;frontend/?.lua;rocks/share/lua/5.1/?.lua;" .. package.path
package.cpath = "common/?.so;common/?.dll;/usr/lib/lua/?.so;rocks/lib/lua/5.1/?.so;" .. package.cpath
-- set search path for 'ffi.load()'
local ffi = require("ffi")
@ -138,7 +138,8 @@ if ARGV[argidx] and ARGV[argidx] ~= "" then
-- the filemanger will show the files in that path
else
local FileManager = require("apps/filemanager/filemanager")
FileManager:showFiles(ARGV[argidx])
local home_dir = G_reader_settings:readSetting("home_dir") or ARGV[argidx]
FileManager:showFiles(home_dir)
end
UIManager:run()
elseif last_file then

@ -9,13 +9,9 @@ describe("HTTP client module", function()
local function response_callback(res)
requests = requests - 1
if requests == 0 then UIManager:quit() end
assert(not res.error, "error occurs")
assert(res.body)
end
local function error_callback(res)
requests = requests - 1
if requests == 0 then UIManager:quit() end
assert(false, "error occurs")
end
local async_client = HTTPClient:new()
it("should get response from async GET request", function()
UIManager:quit()
@ -27,7 +23,7 @@ describe("HTTP client module", function()
for _, url in ipairs(urls) do
async_client:request({
url = url,
}, response_callback, error_callback)
}, response_callback)
end
UIManager:runForever()
end)

@ -0,0 +1,178 @@
package.path = "rocks/share/lua/5.1/?.lua;" .. package.path
package.cpath = "rocks/lib/lua/5.1/?.so;" .. package.cpath
require("commonrequire")
local UIManager = require("ui/uimanager")
local HTTPClient = require("httpclient")
local DEBUG = require("dbg")
local md5 = require("MD5")
--DEBUG:turnOn()
local service = [[
{
"base_url" : "https://192.168.1.101:7200",
"name" : "api",
"methods" : {
"register" : {
"path" : "/users/create",
"method" : "POST",
"required_params" : [
"username",
"password",
],
"payload" : [
"username",
"password",
],
"expected_status" : [201, 402]
},
"authorize" : {
"path" : "/users/auth",
"method" : "GET",
"expected_status" : [200, 401]
},
"update_progress" : {
"path" : "/syncs/progress",
"method" : "PUT",
"required_params" : [
"document",
"progress",
"percentage",
"device",
],
"payload" : [
"document",
"progress",
"percentage",
"device",
],
"expected_status" : [200, 202, 401]
},
"get_progress" : {
"path" : "/syncs/progress/:document",
"method" : "GET",
"required_params" : [
"document",
],
"expected_status" : [200, 401]
},
}
}
]]
describe("KOSync modules #notest #nocov", function()
local Spore = require("Spore")
local client = Spore.new_from_string(service)
package.loaded['Spore.Middleware.GinClient'] = {}
require('Spore.Middleware.GinClient').call = function(self, req)
req.headers['accept'] = "application/vnd.koreader.v1+json"
end
package.loaded['Spore.Middleware.KOSyncAuth'] = {}
require('Spore.Middleware.KOSyncAuth').call = function(args, req)
req.headers['x-auth-user'] = args.username
req.headers['x-auth-key'] = args.userkey
end
-- password should be hashed before submitting to server
local username, password = "koreader", md5:sum("koreader")
-- fake progress data
local doc, percentage, progress, device =
"41cce710f34e5ec21315e19c99821415", -- fast digest of the document
0.356, -- percentage of the progress
"69", -- page number or xpointer
"my kpw" -- device name
it("should create new user", function()
client:reset_middlewares()
client:enable('Format.JSON')
client:enable("GinClient")
local ok, res = pcall(function()
return client:register({
username = username,
password = password,
})
end)
if ok then
if res.status == 200 then
DEBUG("register successful to ", res.body.username)
elseif res.status == 402 then
DEBUG("register unsuccessful: ", res.body.message)
end
else
DEBUG("Please retry later", res)
end
end)
it("should authorize user", function()
client:reset_middlewares()
client:enable('Format.JSON')
client:enable("GinClient")
client:enable("KOSyncAuth", {
username = username,
userkey = password,
})
local ok, res = pcall(function()
return client:authorize()
end)
if ok then
if res.status == 200 then
assert.are.same("OK", res.body.authorized)
else
DEBUG(res.body)
end
else
DEBUG("Please retry later", res)
end
end)
it("should update progress", function()
client:reset_middlewares()
client:enable('Format.JSON')
client:enable("GinClient")
client:enable("KOSyncAuth", {
username = username,
userkey = password,
})
local ok, res = pcall(function()
return client:update_progress({
document = doc,
progress = progress,
percentage = percentage,
device = device,
})
end)
if ok then
if res.status == 200 then
local result = res.body
assert.are.same(progress, result.progress)
assert.are.same(percentage, result.percentage)
assert.are.same(device, result.device)
else
DEBUG(res.body.message)
end
else
DEBUG("Please retry later", res)
end
end)
it("should get progress", function()
client:reset_middlewares()
client:enable('Format.JSON')
client:enable("GinClient")
client:enable("KOSyncAuth", {
username = username,
userkey = password,
})
local ok, res = pcall(function()
return client:get_progress({
document = doc,
})
end)
if ok then
if res.status == 200 then
local result = res.body
assert.are.same(progress, result.progress)
assert.are.same(percentage, result.percentage)
assert.are.same(device, result.device)
else
DEBUG(res.body.message)
end
else
DEBUG("Please retry later", res)
end
end)
end)

@ -0,0 +1,115 @@
package.path = "rocks/share/lua/5.1/?.lua;" .. package.path
package.cpath = "rocks/lib/lua/5.1/?.so;" .. package.cpath
require("commonrequire")
local UIManager = require("ui/uimanager")
local HTTPClient = require("httpclient")
local DEBUG = require("dbg")
--DEBUG:turnOn()
local service = [[
{
"base_url" : "http://httpbin.org",
"name" : "api",
"methods" : {
"get_info" : {
"path" : "/get",
"method" : "GET",
"required_params" : [
"user"
],
"optional_params" : [
"age"
],
},
"post_info" : {
"path" : "/post",
"method" : "POST",
"required_params" : [
"user"
],
"optional_params" : [
"age"
],
"payload" : [
"user",
"age",
],
},
}
}
]]
describe("Lua Spore modules #nocov", function()
local Spore = require("Spore")
local client = Spore.new_from_string(service)
client:enable('Format.JSON')
it("should complete GET request", function()
local info = {user = 'john', age = '25'}
local res = client:get_info(info)
assert.are.same(res.body.args, info)
end)
it("should complete POST request", function()
local info = {user = 'sam', age = '26'}
local res = client:post_info(info)
assert.are.same(res.body.json, info)
end)
end)
describe("Lua Spore modules with async http request #nocov", function()
local Spore = require("Spore")
local client = Spore.new_from_string(service)
local async_http_client = HTTPClient:new()
package.loaded['Spore.Middleware.AsyncHTTP'] = {}
require('Spore.Middleware.AsyncHTTP').call = function(args, req)
req:finalize()
local result
async_http_client:request({
url = req.url,
method = req.method,
body = req.env.spore.payload,
on_headers = function(headers)
for header, value in pairs(req.headers) do
if type(header) == 'string' then
headers:add(header, value)
end
end
end,
}, function(res)
result = res
-- Turbo HTTP client uses code instead of status
-- change to status so that Spore can understand
result.status = res.code
coroutine.resume(args.thread)
UIManager.INPUT_TIMEOUT = 100 -- no need in production
end)
return coroutine.create(function() coroutine.yield(result) end)
end
it("should complete GET request", function()
UIManager:quit()
local co = coroutine.create(function()
local info = {user = 'john', age = '25'}
local res = client:get_info(info)
UIManager:quit()
assert.are.same(res.body.args, info)
end)
client:reset_middlewares()
client:enable("Format.JSON")
client:enable("AsyncHTTP", {thread = co})
coroutine.resume(co)
UIManager:runForever()
end)
it("should complete POST request", function()
UIManager:quit()
local co = coroutine.create(function()
local info = {user = 'sam', age = '26'}
local res = client:post_info(info)
UIManager:quit()
assert.are.same(res.body.json, info)
end)
client:reset_middlewares()
client:enable("Format.JSON")
client:enable("AsyncHTTP", {thread = co})
coroutine.resume(co)
UIManager:runForever()
end)
end)
Loading…
Cancel
Save