From d7faba5b5cb767d0645e346c43759ce2433c17de Mon Sep 17 00:00:00 2001 From: chrox Date: Sat, 7 Mar 2015 11:19:39 +0800 Subject: [PATCH 01/24] request from async http client only accept one callback and error should be checked in the callback --- frontend/httpclient.lua | 12 ++++++------ spec/unit/httpclient_spec.lua | 8 ++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/frontend/httpclient.lua b/frontend/httpclient.lua index d3e02df33..9961edcaa 100644 --- a/frontend/httpclient.lua +++ b/frontend/httpclient.lua @@ -22,7 +22,7 @@ function HTTPClient:removeHeader(header) self.headers[header] = nil end -function HTTPClient:request(request, response_callback, error_callback) +function HTTPClient:request(request, response_callback) request.on_headers = function(headers) for header, value in pairs(self.headers) do headers[header] = value @@ -36,15 +36,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) diff --git a/spec/unit/httpclient_spec.lua b/spec/unit/httpclient_spec.lua index b1c3e8762..f31ef2bf4 100644 --- a/spec/unit/httpclient_spec.lua +++ b/spec/unit/httpclient_spec.lua @@ -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) From 9ab62249636f2da2d3c89a27cd629a873ce91a8d Mon Sep 17 00:00:00 2001 From: chrox Date: Sat, 7 Mar 2015 17:05:24 +0800 Subject: [PATCH 02/24] add lua Spore to build REST client --- base | 2 +- spec/unit/spore_spec.lua | 116 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 spec/unit/spore_spec.lua diff --git a/base b/base index 897906d91..bd29103f8 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit 897906d91a3cbba0eb2aa27583a6510c99ac720d +Subproject commit bd29103f88ae3227be07e2f2aa1e6c7a20c64e25 diff --git a/spec/unit/spore_spec.lua b/spec/unit/spore_spec.lua new file mode 100644 index 000000000..326f006b5 --- /dev/null +++ b/spec/unit/spore_spec.lua @@ -0,0 +1,116 @@ +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 request #nocov", function() + local Spore = require("Spore") + local client = Spore.new_from_string(service) + client:enable("Format.JSON") + package.loaded['Spore.Middleware.Async'] = {} + local async_http_client = HTTPClient:new() + 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) + require('Spore.Middleware.Async').call = function(self, req) + req:finalize() + local result + async_http_client:request({ + url = req.url, + method = req.method, + }, function(res) + result = res + coroutine.resume(co) + UIManager.INPUT_TIMEOUT = 100 -- no need in production + end) + return coroutine.create(function() coroutine.yield(result) end) + end + client:enable("Async") + coroutine.resume(co) + UIManager.INPUT_TIMEOUT = 100 + 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) + require('Spore.Middleware.Async').call = function(self, req) + req:finalize() + local result + async_http_client:request({ + url = req.url, + method = req.method, + }, function(res) + result = res + coroutine.resume(co) + UIManager.INPUT_TIMEOUT = 100 -- no need in production + end) + return coroutine.create(function() coroutine.yield(result) end) + end + client:enable("Async") + coroutine.resume(co) + UIManager.INPUT_TIMEOUT = 100 + UIManager:runForever() + end) +end) From d08e22ec2e185f835b701552ece15ef539560196 Mon Sep 17 00:00:00 2001 From: chrox Date: Mon, 9 Mar 2015 20:20:32 +0800 Subject: [PATCH 03/24] add simple sync service as a plugin The 'KOSync' plugin will synchronize furthest reading progress across different koreader devices after users registering their devices. The synchronizing service is open-sourced as the project [koreader/koreader-sync-server](https://github.com/koreader/koreader-sync-server). --- .editorconfig | 2 +- frontend/apps/reader/modules/readerpaging.lua | 12 +- .../apps/reader/modules/readerrolling.lua | 4 + frontend/apps/reader/readerui.lua | 5 +- frontend/httpclient.lua | 14 - plugins/kosync.koplugin/KOSyncClient.lua | 150 ++++++++++ plugins/kosync.koplugin/api.json | 49 ++++ plugins/kosync.koplugin/main.lua | 272 ++++++++++++++++++ reader.lua | 4 +- spec/unit/kosync_spec.lua | 178 ++++++++++++ spec/unit/spore_spec.lua | 65 +++-- 11 files changed, 702 insertions(+), 53 deletions(-) create mode 100644 plugins/kosync.koplugin/KOSyncClient.lua create mode 100644 plugins/kosync.koplugin/api.json create mode 100644 plugins/kosync.koplugin/main.lua create mode 100644 spec/unit/kosync_spec.lua diff --git a/.editorconfig b/.editorconfig index 2395ff823..10681860d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -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 diff --git a/frontend/apps/reader/modules/readerpaging.lua b/frontend/apps/reader/modules/readerpaging.lua index 39d672896..c230d087e 100644 --- a/frontend/apps/reader/modules/readerpaging.lua +++ b/frontend/apps/reader/modules/readerpaging.lua @@ -131,10 +131,20 @@ 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) 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) -- FIXME: repeated code with page overlap menu for readerrolling -- needs to keep only one copy of the logic as for the DRY principle. diff --git a/frontend/apps/reader/modules/readerrolling.lua b/frontend/apps/reader/modules/readerrolling.lua index 83b4effad..2619f8214 100644 --- a/frontend/apps/reader/modules/readerrolling.lua +++ b/frontend/apps/reader/modules/readerrolling.lua @@ -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. diff --git a/frontend/apps/reader/readerui.lua b/frontend/apps/reader/readerui.lua index c23d5d709..4418e854a 100644 --- a/frontend/apps/reader/readerui.lua +++ b/frontend/apps/reader/readerui.lua @@ -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 diff --git a/frontend/httpclient.lua b/frontend/httpclient.lua index 9961edcaa..fb2668435 100644 --- a/frontend/httpclient.lua +++ b/frontend/httpclient.lua @@ -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) - request.on_headers = function(headers) - for header, value in pairs(self.headers) do - headers[header] = value - end - end request.connect_timeout = 10 request.request_timeout = 20 UIManager:initLooper() diff --git a/plugins/kosync.koplugin/KOSyncClient.lua b/plugins/kosync.koplugin/KOSyncClient.lua new file mode 100644 index 000000000..55986dc8f --- /dev/null +++ b/plugins/kosync.koplugin/KOSyncClient.lua @@ -0,0 +1,150 @@ +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 + 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 diff --git a/plugins/kosync.koplugin/api.json b/plugins/kosync.koplugin/api.json new file mode 100644 index 000000000..3db2673cb --- /dev/null +++ b/plugins/kosync.koplugin/api.json @@ -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] + }, + } +} diff --git a/plugins/kosync.koplugin/main.lua b/plugins/kosync.koplugin/main.lua new file mode 100644 index 000000000..bd3275d19 --- /dev/null +++ b/plugins/kosync.koplugin/main.lua @@ -0,0 +1,272 @@ +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 DEBUG = require("dbg") +local T = require("ffi/util").template +local _ = require("gettext") +local md5 = require("MD5") + +local KOSync = InputContainer:new{ + name = "kosync", + register_title = _("Register an account in Koreader server"), + login_title = _("Login to Koreader server"), +} + +function KOSync:init() + local settings = G_reader_settings:readSetting("kosync") or {} + self.kosync_username = settings.username or "" + self.kosync_userkey = settings.userkey + self.ui:registerPostInitCallback(function() + UIManager:scheduleIn(1, function() self:getProgress() 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, + }, + } + }) +end + +function KOSync:login() + if NetworkMgr:getWifiStatus() == false then + NetworkMgr:promptWifiOn() + end + self.login_dialog = LoginDialog:new{ + title = self.kosync_username and self.login_title or self.register_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 = not self.kosync and true or false, + 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_username = nil + self.kosync_userkey = nil + 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() + 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 percentage = self:getLastPercent() + DEBUG("current progress", percentage) + if (body.percentage - percentage) > 0.0001 then + UIManager:show(ConfirmBox:new{ + text = T(_("Sync to furthest location from '%1'?"), + body.device), + ok_callback = function() + self:syncToProgress(body.progress) + end, + }) + 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, + } + G_reader_settings:saveSetting("kosync", settings) +end + +function KOSync:onCloseDocument() + DEBUG("on close document") + self:updateProgress() +end + +return KOSync diff --git a/reader.lua b/reader.lua index fa3441d89..81484714d 100755 --- a/reader.lua +++ b/reader.lua @@ -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") diff --git a/spec/unit/kosync_spec.lua b/spec/unit/kosync_spec.lua new file mode 100644 index 000000000..9a77352b0 --- /dev/null +++ b/spec/unit/kosync_spec.lua @@ -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) diff --git a/spec/unit/spore_spec.lua b/spec/unit/spore_spec.lua index 326f006b5..612bc50db 100644 --- a/spec/unit/spore_spec.lua +++ b/spec/unit/spore_spec.lua @@ -55,12 +55,35 @@ describe("Lua Spore modules #nocov", function() end) end) -describe("Lua Spore modules with async request #nocov", function() +describe("Lua Spore modules with async http request #nocov", function() local Spore = require("Spore") local client = Spore.new_from_string(service) - client:enable("Format.JSON") - package.loaded['Spore.Middleware.Async'] = {} 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() @@ -69,22 +92,10 @@ describe("Lua Spore modules with async request #nocov", function() UIManager:quit() assert.are.same(res.body.args, info) end) - require('Spore.Middleware.Async').call = function(self, req) - req:finalize() - local result - async_http_client:request({ - url = req.url, - method = req.method, - }, function(res) - result = res - coroutine.resume(co) - UIManager.INPUT_TIMEOUT = 100 -- no need in production - end) - return coroutine.create(function() coroutine.yield(result) end) - end - client:enable("Async") + client:reset_middlewares() + client:enable("Format.JSON") + client:enable("AsyncHTTP", {thread = co}) coroutine.resume(co) - UIManager.INPUT_TIMEOUT = 100 UIManager:runForever() end) it("should complete POST request", function() @@ -95,22 +106,10 @@ describe("Lua Spore modules with async request #nocov", function() UIManager:quit() assert.are.same(res.body.json, info) end) - require('Spore.Middleware.Async').call = function(self, req) - req:finalize() - local result - async_http_client:request({ - url = req.url, - method = req.method, - }, function(res) - result = res - coroutine.resume(co) - UIManager.INPUT_TIMEOUT = 100 -- no need in production - end) - return coroutine.create(function() coroutine.yield(result) end) - end - client:enable("Async") + client:reset_middlewares() + client:enable("Format.JSON") + client:enable("AsyncHTTP", {thread = co}) coroutine.resume(co) - UIManager.INPUT_TIMEOUT = 100 UIManager:runForever() end) end) From 3984d5b31d82926694c042a8697f667d25c4c30c Mon Sep 17 00:00:00 2001 From: chrox Date: Mon, 9 Mar 2015 20:44:37 +0800 Subject: [PATCH 04/24] disable debug in unit test --- spec/unit/kosync_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/kosync_spec.lua b/spec/unit/kosync_spec.lua index 9a77352b0..788a70a2a 100644 --- a/spec/unit/kosync_spec.lua +++ b/spec/unit/kosync_spec.lua @@ -5,7 +5,7 @@ local UIManager = require("ui/uimanager") local HTTPClient = require("httpclient") local DEBUG = require("dbg") local md5 = require("MD5") -DEBUG:turnOn() +--DEBUG:turnOn() local service = [[ { From 9701a49a8c1f02d1a8e4832561b3b2a32b45e0dc Mon Sep 17 00:00:00 2001 From: chrox Date: Tue, 10 Mar 2015 09:43:53 +0800 Subject: [PATCH 05/24] fix luarock native build for pocketbook --- base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base b/base index bd29103f8..fb6e46cbb 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit bd29103f88ae3227be07e2f2aa1e6c7a20c64e25 +Subproject commit fb6e46cbbacd0e80dd6a9fac9fc6ba4f30d688eb From 25c7687c7dbd9c1ae450bf2737696233794b171f Mon Sep 17 00:00:00 2001 From: chrox Date: Tue, 10 Mar 2015 15:09:42 +0800 Subject: [PATCH 06/24] fix koplugin on Android --- base | 2 +- plugins/kosync.koplugin/KOSyncClient.lua | 2 ++ plugins/kosync.koplugin/main.lua | 44 ++++++++++++++++++++---- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/base b/base index fb6e46cbb..1a4c1faaf 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit fb6e46cbbacd0e80dd6a9fac9fc6ba4f30d688eb +Subproject commit 1a4c1faafdd218f476af890009a2bdf40aa5909f diff --git a/plugins/kosync.koplugin/KOSyncClient.lua b/plugins/kosync.koplugin/KOSyncClient.lua index 55986dc8f..98ca2a2ab 100644 --- a/plugins/kosync.koplugin/KOSyncClient.lua +++ b/plugins/kosync.koplugin/KOSyncClient.lua @@ -47,6 +47,8 @@ function KOSyncClient:init() -- 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) diff --git a/plugins/kosync.koplugin/main.lua b/plugins/kosync.koplugin/main.lua index bd3275d19..d1159e317 100644 --- a/plugins/kosync.koplugin/main.lua +++ b/plugins/kosync.koplugin/main.lua @@ -8,6 +8,7 @@ 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") @@ -23,8 +24,11 @@ function KOSync:init() local settings = G_reader_settings:readSetting("kosync") or {} self.kosync_username = settings.username or "" self.kosync_userkey = settings.userkey + self.kosync_auto_sync = settings.auto_sync or true self.ui:registerPostInitCallback(function() - UIManager:scheduleIn(1, function() self:getProgress() end) + if self.kosync_auto_sync then + UIManager:scheduleIn(1, function() self:getProgress() end) + end end) self.ui.menu:registerToMainMenu(self) end @@ -44,6 +48,23 @@ function KOSync:addToMainMenu(tab_item_table) 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 @@ -174,8 +195,8 @@ function KOSync:doLogin(username, password) end function KOSync:logout() - self.kosync_username = nil self.kosync_userkey = nil + self.kosync_auto_sync = true self:onSaveSettings() end @@ -225,7 +246,7 @@ function KOSync:updateProgress() end end -function KOSync:getProgress() +function KOSync:getProgress(manual) if self.kosync_username and self.kosync_userkey then local KOSyncClient = require("KOSyncClient") local client = KOSyncClient:new{ @@ -237,16 +258,22 @@ function KOSync:getProgress() 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) > 0.0001 then + if body.percentage > percentage and body.progress ~= progress then UIManager:show(ConfirmBox:new{ - text = T(_("Sync to furthest location from '%1'?"), - body.device), + 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 = 1.0, + }) end end end) @@ -260,13 +287,16 @@ 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") - self:updateProgress() + if self.kosync_auto_sync then + self:updateProgress() + end end return KOSync From 2a373e5f5b58b377212f254f71365e7e52a2a6fe Mon Sep 17 00:00:00 2001 From: chrox Date: Tue, 10 Mar 2015 15:12:44 +0800 Subject: [PATCH 07/24] keep plugin naming consistent --- plugins/kosync.koplugin/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/kosync.koplugin/main.lua b/plugins/kosync.koplugin/main.lua index d1159e317..7abca9cc7 100644 --- a/plugins/kosync.koplugin/main.lua +++ b/plugins/kosync.koplugin/main.lua @@ -35,7 +35,7 @@ end function KOSync:addToMainMenu(tab_item_table) table.insert(tab_item_table.plugins, { - text = _("Progress Sync"), + text = _("Progress sync"), sub_item_table = { { text_func = function() From 064992e3c199a05bfc853dd0cd2bf30c177629ee Mon Sep 17 00:00:00 2001 From: chrox Date: Tue, 10 Mar 2015 15:49:33 +0800 Subject: [PATCH 08/24] localizations for messages from server --- plugins/kosync.koplugin/main.lua | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/plugins/kosync.koplugin/main.lua b/plugins/kosync.koplugin/main.lua index 7abca9cc7..c696c764d 100644 --- a/plugins/kosync.koplugin/main.lua +++ b/plugins/kosync.koplugin/main.lua @@ -14,15 +14,20 @@ 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", - register_title = _("Register an account in Koreader server"), - login_title = _("Login to Koreader server"), + title = _("Register/login to Koreader server"), } function KOSync:init() local settings = G_reader_settings:readSetting("kosync") or {} - self.kosync_username = settings.username or "" + self.kosync_username = settings.username self.kosync_userkey = settings.userkey self.kosync_auto_sync = settings.auto_sync or true self.ui:registerPostInitCallback(function() @@ -49,7 +54,7 @@ function KOSync:addToMainMenu(tab_item_table) end, }, { - text = _("Auto Sync"), + text = _("Auto sync"), checked_func = function() return self.kosync_auto_sync end, callback = function() self.kosync_auto_sync = not self.kosync_auto_sync @@ -74,7 +79,7 @@ function KOSync:login() NetworkMgr:promptWifiOn() end self.login_dialog = LoginDialog:new{ - title = self.kosync_username and self.login_title or self.register_title, + title = self.title, username = self.kosync_username or "", buttons = { { @@ -103,7 +108,7 @@ function KOSync:login() }, { text = _("Register"), - enabled = not self.kosync and true or false, + enabled = true, callback = function() local username, password = self:getCredential() self:closeDialog() @@ -272,7 +277,7 @@ function KOSync:getProgress(manual) elseif manual and body.progress == progress then UIManager:show(InfoMessage:new{ text = _("We are already synchronized."), - timeout = 1.0, + timeout = 3, }) end end From ae9f99744df646ce8a933e6dd1f21c0477f76190 Mon Sep 17 00:00:00 2001 From: chrox Date: Tue, 10 Mar 2015 18:33:11 +0800 Subject: [PATCH 09/24] flush setting before showing screensaver --- frontend/device/generic/device.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/device/generic/device.lua b/frontend/device/generic/device.lua index df63d11f0..14b7e1c45 100644 --- a/frontend/device/generic/device.lua +++ b/frontend/device/generic/device.lua @@ -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...") From def60ff8288eac7dd6ecb04b0179c1b3d1696caf Mon Sep 17 00:00:00 2001 From: chrox Date: Tue, 10 Mar 2015 18:33:45 +0800 Subject: [PATCH 10/24] update koreader-base to ship libssl and libcrypto on all platforms --- base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base b/base index 1a4c1faaf..10326e980 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit 1a4c1faafdd218f476af890009a2bdf40aa5909f +Subproject commit 10326e9800b9e0ececf025a1b421c51fa427cd0e From d1ed475b18bb60d3186ed2eaaf68161601e2a070 Mon Sep 17 00:00:00 2001 From: chrox Date: Wed, 11 Mar 2015 12:21:50 +0800 Subject: [PATCH 11/24] fix different libssl libraries used in luasec and turbo may cause segfault now we don't use system libssl and libcrypto at all. --- base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base b/base index 10326e980..f7bfcd47c 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit 10326e9800b9e0ececf025a1b421c51fa427cd0e +Subproject commit f7bfcd47c68142e509af188d3f46cf2dea217405 From 055d3b0127b05b099672cf4db997e9b48b344504 Mon Sep 17 00:00:00 2001 From: chrox Date: Wed, 11 Mar 2015 12:23:23 +0800 Subject: [PATCH 12/24] fix auto sync cannot be disabled --- plugins/kosync.koplugin/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/kosync.koplugin/main.lua b/plugins/kosync.koplugin/main.lua index c696c764d..57180244b 100644 --- a/plugins/kosync.koplugin/main.lua +++ b/plugins/kosync.koplugin/main.lua @@ -29,7 +29,7 @@ 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 = settings.auto_sync or true + 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) From c7d4df806ad06a38729ec89a208149b9209d9adf Mon Sep 17 00:00:00 2001 From: chrox Date: Wed, 11 Mar 2015 17:35:59 +0800 Subject: [PATCH 13/24] fix loading shared library on Android This should fix #1447 and maybe also #1416. --- platform/android/luajit-launcher | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/android/luajit-launcher b/platform/android/luajit-launcher index 576b42e8a..64caed002 160000 --- a/platform/android/luajit-launcher +++ b/platform/android/luajit-launcher @@ -1 +1 @@ -Subproject commit 576b42e8aedd29af20415fd840f26623258caaa3 +Subproject commit 64caed00259bb23b789f2a8051e77efa9bcde9cc From cf124eb72f3d14a5eb9c4e19992d90fded291582 Mon Sep 17 00:00:00 2001 From: chrox Date: Thu, 12 Mar 2015 14:19:10 +0800 Subject: [PATCH 14/24] refresh menu instead of closing if menu item is a check option --- frontend/ui/widget/touchmenu.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/ui/widget/touchmenu.lua b/frontend/ui/widget/touchmenu.lua index ad766396e..f3d8a3f80 100644 --- a/frontend/ui/widget/touchmenu.lua +++ b/frontend/ui/widget/touchmenu.lua @@ -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 From 35abf4bfaf36f174e8fe106fc53053d76ce0fbc8 Mon Sep 17 00:00:00 2001 From: chrox Date: Thu, 12 Mar 2015 14:35:43 +0800 Subject: [PATCH 15/24] move minibar configurations to reader menu and fix #1446 by adding progress percentage in minibar. --- defaults.lua | 11 -- frontend/apps/reader/modules/readerfooter.lua | 112 ++++++++++++++---- frontend/apps/reader/modules/readerview.lua | 13 +- 3 files changed, 103 insertions(+), 33 deletions(-) diff --git a/defaults.lua b/defaults.lua index 9f46d0de8..8ab103cd1 100644 --- a/defaults.lua +++ b/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 diff --git a/frontend/apps/reader/modules/readerfooter.lua b/frontend/apps/reader/modules/readerfooter.lua index 4ff89c975..2af0f9d7f 100644 --- a/frontend/apps/reader/modules/readerfooter.lua +++ b/frontend/apps/reader/modules/readerfooter.lua @@ -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, + left_pages = 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.left_pages 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"), + left_pages = ("Left pages 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("left_pages"), + 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.left_pages 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.left_pages 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() diff --git a/frontend/apps/reader/modules/readerview.lua b/frontend/apps/reader/modules/readerview.lua index dfb373a1d..e76a71f75 100644 --- a/frontend/apps/reader/modules/readerview.lua +++ b/frontend/apps/reader/modules/readerview.lua @@ -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 From ea76d91ed4a8f414cb82b453c4274444ca54e8f2 Mon Sep 17 00:00:00 2001 From: chrox Date: Thu, 12 Mar 2015 14:52:55 +0800 Subject: [PATCH 16/24] save zoom mode in flipping mode This is a workaround to fix #1439. --- frontend/apps/reader/modules/readerpaging.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/apps/reader/modules/readerpaging.lua b/frontend/apps/reader/modules/readerpaging.lua index c230d087e..907c8112d 100644 --- a/frontend/apps/reader/modules/readerpaging.lua +++ b/frontend/apps/reader/modules/readerpaging.lua @@ -126,6 +126,7 @@ 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() @@ -133,6 +134,7 @@ function ReaderPaging:onSaveSettings() self.ui.doc_settings:saveSetting("last_page", self:getTopPage()) 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() @@ -259,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() @@ -268,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 From 992e769aafbe95268b0002316f58cc2f17682464 Mon Sep 17 00:00:00 2001 From: chrox Date: Thu, 12 Mar 2015 15:45:58 +0800 Subject: [PATCH 17/24] support goto relative page number We can input relative page number now in the reader goto dialog. Goto "+4" will page forward 4 pages and goto "-4" will page backward 4 pages. This implements #1437. --- frontend/apps/reader/modules/readergoto.lua | 30 ++++++++----------- frontend/apps/reader/modules/readerpaging.lua | 5 ++++ .../apps/reader/modules/readerrolling.lua | 8 +++++ 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/frontend/apps/reader/modules/readergoto.lua b/frontend/apps/reader/modules/readergoto.lua index a66ab269f..5ec541160 100644 --- a/frontend/apps/reader/modules/readergoto.lua +++ b/frontend/apps/reader/modules/readergoto.lua @@ -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 diff --git a/frontend/apps/reader/modules/readerpaging.lua b/frontend/apps/reader/modules/readerpaging.lua index 907c8112d..dc3a78e52 100644 --- a/frontend/apps/reader/modules/readerpaging.lua +++ b/frontend/apps/reader/modules/readerpaging.lua @@ -799,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 diff --git a/frontend/apps/reader/modules/readerrolling.lua b/frontend/apps/reader/modules/readerrolling.lua index 2619f8214..6522b2910 100644 --- a/frontend/apps/reader/modules/readerrolling.lua +++ b/frontend/apps/reader/modules/readerrolling.lua @@ -331,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 From 3224eb1797d759537231dfd2c2e3fd6834d7b043 Mon Sep 17 00:00:00 2001 From: chrox Date: Thu, 12 Mar 2015 15:51:59 +0800 Subject: [PATCH 18/24] fix inproper variable name for pages left --- frontend/apps/reader/modules/readerfooter.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/apps/reader/modules/readerfooter.lua b/frontend/apps/reader/modules/readerfooter.lua index 2af0f9d7f..0d0da936d 100644 --- a/frontend/apps/reader/modules/readerfooter.lua +++ b/frontend/apps/reader/modules/readerfooter.lua @@ -46,7 +46,7 @@ function ReaderFooter:init() battery = true, time = true, page_progress = true, - left_pages = true, + pages_left = true, percentage = true, } local text_default = "" @@ -61,7 +61,7 @@ function ReaderFooter:init() if self.settings.page_progress then table.insert(info, "0000 / 0000") end - if self.settings.left_pages then + if self.settings.pages_left then table.insert(info, "=> 000") end if self.settings.percentage then @@ -157,7 +157,7 @@ local options = { battery = _("Battery status"), time = _("Current time"), page_progress = ("Current page"), - left_pages = ("Left pages in this chapter"), + pages_left = ("Pages left in this chapter"), percentage = ("Progress percentage"), } @@ -188,7 +188,7 @@ function ReaderFooter:addToMainMenu(tab_item_table) get_minibar_option("battery"), get_minibar_option("time"), get_minibar_option("page_progress"), - get_minibar_option("left_pages"), + get_minibar_option("pages_left"), get_minibar_option("percentage"), } }) @@ -231,7 +231,7 @@ function ReaderFooter:updateFooterPage() if self.settings.page_progress then table.insert(info, self:getProgressInfo()) end - if self.settings.left_pages then + if self.settings.pages_left then table.insert(info, self:getNextChapterInfo()) end if self.settings.percentage then @@ -329,7 +329,7 @@ function ReaderFooter:onTapFooter(arg, ges) if (self.mode == 2) and not self.settings.time then self.mode = 3 end - if (self.mode == 3) and not self.settings.left_pages then + if (self.mode == 3) and not self.settings.pages_left then self.mode = 4 end if (self.mode == 4) and not self.settings.battery then From 69e6b6b9cbf0a0ad06f5cef5ffedabe6da93947a Mon Sep 17 00:00:00 2001 From: chrox Date: Thu, 12 Mar 2015 16:29:15 +0800 Subject: [PATCH 19/24] hold on directory in filemanager can set the it as HOME directory and this "HOME" directory will override the command line option passed to koreader. This patch implements #1434. --- frontend/apps/filemanager/filemanager.lua | 93 +++++++++++++---------- reader.lua | 3 +- 2 files changed, 55 insertions(+), 41 deletions(-) diff --git a/frontend/apps/filemanager/filemanager.lua b/frontend/apps/filemanager/filemanager.lua index 52cb23bea..6ba1e128b 100644 --- a/frontend/apps/filemanager/filemanager.lua +++ b/frontend/apps/filemanager/filemanager.lua @@ -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 diff --git a/reader.lua b/reader.lua index 81484714d..fff81b540 100755 --- a/reader.lua +++ b/reader.lua @@ -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 From daeefff741d0ddfec2d2833e361017e5233f8729 Mon Sep 17 00:00:00 2001 From: chrox Date: Thu, 12 Mar 2015 16:31:12 +0800 Subject: [PATCH 20/24] fix #1433 --- frontend/document/djvudocument.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/document/djvudocument.lua b/frontend/document/djvudocument.lua index 084213965..3c83aa368 100644 --- a/frontend/document/djvudocument.lua +++ b/frontend/document/djvudocument.lua @@ -123,6 +123,7 @@ end function DjvuDocument:register(registry) registry:addProvider("djvu", "application/djvu", self) + registry:addProvider("djv", "application/djvu", self) end return DjvuDocument From f92da6c14cc27dfc232a400aaa8415354a1157b4 Mon Sep 17 00:00:00 2001 From: chrox Date: Thu, 12 Mar 2015 16:49:32 +0800 Subject: [PATCH 21/24] fix #1432 --- base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base b/base index f7bfcd47c..7e39b9692 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit f7bfcd47c68142e509af188d3f46cf2dea217405 +Subproject commit 7e39b96927d11083679e8329874da6ec92501d00 From 83cad1c61c06c3f20c1f7aadb71736f43ba4563a Mon Sep 17 00:00:00 2001 From: chrox Date: Thu, 12 Mar 2015 17:31:33 +0800 Subject: [PATCH 22/24] larger page margin as a workaround to fix #1422 --- frontend/ui/data/koptoptions.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/ui/data/koptoptions.lua b/frontend/ui/data/koptoptions.lua index 7171ff129..244de6c23 100644 --- a/frontend/ui/data/koptoptions.lua +++ b/frontend/ui/data/koptoptions.lua @@ -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", }, From 2ad21dcaa27d678cef7117f4d118cc56f3e3dfaf Mon Sep 17 00:00:00 2001 From: chrox Date: Thu, 12 Mar 2015 17:41:20 +0800 Subject: [PATCH 23/24] only strip punctuations when highlight to search This should fix #1419. --- frontend/apps/reader/modules/readerhighlight.lua | 3 ++- frontend/apps/reader/modules/readersearch.lua | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/apps/reader/modules/readerhighlight.lua b/frontend/apps/reader/modules/readerhighlight.lua index 351492331..9e79d9011 100644 --- a/frontend/apps/reader/modules/readerhighlight.lua +++ b/frontend/apps/reader/modules/readerhighlight.lua @@ -468,7 +468,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 diff --git a/frontend/apps/reader/modules/readersearch.lua b/frontend/apps/reader/modules/readersearch.lua index 342d525e4..a21aba836 100644 --- a/frontend/apps/reader/modules/readersearch.lua +++ b/frontend/apps/reader/modules/readersearch.lua @@ -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) From 90a5e09bdcaaf98a71058ec5b94cd2290907d113 Mon Sep 17 00:00:00 2001 From: chrox Date: Thu, 12 Mar 2015 18:50:57 +0800 Subject: [PATCH 24/24] disable highlight in dict window if it's highlighted already This should fix #1418. --- .../apps/reader/modules/readerbookmark.lua | 17 ++++++++++ .../apps/reader/modules/readerhighlight.lua | 32 +++++++++++++------ frontend/ui/widget/dictquicklookup.lua | 19 ++++++++++- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/frontend/apps/reader/modules/readerbookmark.lua b/frontend/apps/reader/modules/readerbookmark.lua index 841b7f45e..da9a8f20d 100644 --- a/frontend/apps/reader/modules/readerbookmark.lua +++ b/frontend/apps/reader/modules/readerbookmark.lua @@ -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 diff --git a/frontend/apps/reader/modules/readerhighlight.lua b/frontend/apps/reader/modules/readerhighlight.lua index 9e79d9011..91e9e1950 100644 --- a/frontend/apps/reader/modules/readerhighlight.lua +++ b/frontend/apps/reader/modules/readerhighlight.lua @@ -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 diff --git a/frontend/ui/widget/dictquicklookup.lua b/frontend/ui/widget/dictquicklookup.lua index 2ff62936a..2ec2883ac 100644 --- a/frontend/ui/widget/dictquicklookup.lua +++ b/frontend/ui/widget/dictquicklookup.lua @@ -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