mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Merge pull request #1448 from chrox/sync_progress
add progress sync plugin and various fixes
This commit is contained in:
@@ -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
|
||||
|
||||
2
base
2
base
Submodule base updated: 897906d91a...7e39b96927
11
defaults.lua
11
defaults.lua
@@ -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,51 +75,64 @@ 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 = _("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 = _("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 = _("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
|
||||
|
||||
@@ -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())
|
||||
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
|
||||
|
||||
function ReaderGoto:gotoLocation()
|
||||
local number = tonumber(self.goto_dialog:getInputText())
|
||||
if number then
|
||||
self.ui:handleEvent(Event:new("GotoPage", number))
|
||||
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
|
||||
|
||||
Submodule platform/android/luajit-launcher updated: 576b42e8ae...64caed0025
152
plugins/kosync.koplugin/KOSyncClient.lua
Normal file
152
plugins/kosync.koplugin/KOSyncClient.lua
Normal file
@@ -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
|
||||
49
plugins/kosync.koplugin/api.json
Normal file
49
plugins/kosync.koplugin/api.json
Normal file
@@ -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]
|
||||
},
|
||||
}
|
||||
}
|
||||
307
plugins/kosync.koplugin/main.lua
Normal file
307
plugins/kosync.koplugin/main.lua
Normal file
@@ -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)
|
||||
|
||||
178
spec/unit/kosync_spec.lua
Normal file
178
spec/unit/kosync_spec.lua
Normal file
@@ -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)
|
||||
115
spec/unit/spore_spec.lua
Normal file
115
spec/unit/spore_spec.lua
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user