From f9ac8b138b95375eab47448fa2d78b79ae024658 Mon Sep 17 00:00:00 2001 From: Ben Kelly Date: Mon, 1 Jan 2018 09:40:28 -0500 Subject: [PATCH] Basic fixes to calibre-sync (#3558) * Properly create intermediate directories when receiving books from Calibre. This fixes an issue where you can't receive books except into directories that already exist on the Kobo, which, in particular, causes problems when your configuration in Calibre is something like "put books in $Author/$Title.epub" and you haven't previously synced any books by that author. * Wake up periodically to process ZMQs if any are registered. This fixes an issue where if there are any timed events (such as the suspend timer) in the queue, ZMQ events may not get processed until the timed event fires, which is a problem when (for example) the suspend timer goes off in an hour and you have something trying to send a book to the kobo over wifi *right now*. With this change, the event loop will wake up every 50ms to check for ZMQ events and process them if necessary. If there are no ZMQs registered (which is typical), it uses the original behaviour -- so this won't affect battery life under normal usage. --- frontend/ui/uimanager.lua | 78 +++++++++++++--------- frontend/util.lua | 30 ++++++++- plugins/calibrecompanion.koplugin/main.lua | 2 + 3 files changed, 77 insertions(+), 33 deletions(-) diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index b2c0e1541..a0f63d0c8 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -21,6 +21,9 @@ local UIManager = { G_reader_settings:readSetting("full_refresh_count") or DRCOUNTMAX, refresh_count = 0, + -- How long to wait between ZMQ wakeups: 50ms. + ZMQ_TIMEOUT = 50 * 1000, + event_handlers = nil, _running = true, @@ -660,6 +663,27 @@ function UIManager:resetInputTimeout() self.INPUT_TIMEOUT = nil end +function UIManager:handleInputEvent(input_event) + if input_event.handler ~= "onInputError" then + self.event_hook:execute("InputEvent", input_event) + end + local handler = self.event_handlers[input_event] + if handler then + handler(input_event) + else + self.event_handlers["__default__"](input_event) + end +end + +-- Process all pending events on all registered ZMQs. +function UIManager:processZMQs() + for _, zeromq in ipairs(self._zeromqs) do + for input_event in zeromq.waitEvent,zeromq do + self:handleInputEvent(input_event) + end + end +end + function UIManager:handleInput() local wait_until, now -- run this in a loop, so that paints can trigger events @@ -683,42 +707,32 @@ function UIManager:handleInput() self:_repaint() until not self._task_queue_dirty - -- wait for next event - -- note that we will skip that if we have tasks that are ready to run - local input_event = nil - if not wait_until then - if #self._zeromqs > 0 then - -- pending message queue, wait 100ms for input - input_event = Input:waitEvent(1000*100) - if not input_event or input_event.handler == "onInputError" then - for _, zeromq in ipairs(self._zeromqs) do - input_event = zeromq:waitEvent() - if input_event then break end - end - end - else - -- no pending task, wait without timeout - input_event = Input:waitEvent(self.INPUT_TIMEOUT) - end - elseif wait_until[1] > now[1] - or wait_until[1] == now[1] and wait_until[2] > now[2] then - -- wait until next task is pending - local wait_us = (wait_until[1] - now[1]) * MILLION - + (wait_until[2] - now[2]) - input_event = Input:waitEvent(wait_us) + -- run ZMQs if any + self:processZMQs() + + -- Figure out how long to wait. + -- Default to INPUT_TIMEOUT (which may be nil, i.e. block until an event happens). + local wait_us = self.INPUT_TIMEOUT + + -- If there's a timed event pending, that puts an upper bound on how long to wait. + if wait_until then + wait_us = math.min( + wait_us or math.huge, + (wait_until[1] - now[1]) * MILLION + + (wait_until[2] - now[2])) + end + + -- If we have any ZMQs registered, ZMQ_TIMEOUT is another upper bound. + if #self._zeromqs > 0 then + wait_us = math.min(wait_us or math.huge, self.ZMQ_TIMEOUT) end + -- wait for next event + local input_event = Input:waitEvent(wait_us) + -- delegate input_event to handler if input_event then - if input_event.handler ~= "onInputError" then - self.event_hook:execute("InputEvent", input_event) - end - local handler = self.event_handlers[input_event] - if handler then - handler(input_event) - else - self.event_handlers["__default__"](input_event) - end + self:handleInputEvent(input_event) end if self.looper then diff --git a/frontend/util.lua b/frontend/util.lua index 9737b17e3..c3fd55230 100644 --- a/frontend/util.lua +++ b/frontend/util.lua @@ -304,6 +304,32 @@ function util.isEmptyDir(path) return true end +--- Checks if the given path exists. Doesn't care if it's a file or directory. +---- @string path +---- @treturn bool +function util.pathExists(path) + local lfs = require("libs/libkoreader-lfs") + return lfs.attributes(path, "mode") ~= nil +end + +--- As `mkdir -p`. +--- Unlike lfs.mkdir(), does not error if the directory already exists, and +--- creates intermediate directories as needed. +---- @string path the directory to create +---- @treturn bool true on success; nil, err_message on error +function util.makePath(path) + path = path:gsub("/+$", "") + if util.pathExists(path) then return true end + + local success, err = util.makePath((util.splitFilePathName(path))) + if not success then + return nil, err.." (creating "..path..")" + end + + local lfs = require("libs/libkoreader-lfs") + return lfs.mkdir(path) +end + --- Replaces characters that are invalid filenames. -- -- Replaces the characters \/:*?"<>| with an _. @@ -326,7 +352,9 @@ function util.replaceSlashChar(str) end end ---- Splits a file into its path and name +--- Splits a file into its directory path and file name. +--- If the given path has a trailing /, returns the entire path as the directory +--- path and "" as the file name. ---- @string file ---- @treturn string path, filename function util.splitFilePathName(file) diff --git a/plugins/calibrecompanion.koplugin/main.lua b/plugins/calibrecompanion.koplugin/main.lua index 6349f73f5..4598b0828 100644 --- a/plugins/calibrecompanion.koplugin/main.lua +++ b/plugins/calibrecompanion.koplugin/main.lua @@ -5,6 +5,7 @@ local JSON = require("json") local DEBUG = require("dbg") local _ = require("gettext") local NetworkMgr = require("ui/network/manager") +local util = require("frontend/util") require("ffi/zeromq_h") @@ -320,6 +321,7 @@ function CalibreCompanion:sendBook(arg) local inbox_dir = G_reader_settings:readSetting("inbox_dir") local filename = inbox_dir .. "/" .. arg.lpath DEBUG("write to file", filename) + util.makePath((util.splitFilePathName(filename))) local outfile = io.open(filename, "wb") local to_write_bytes = arg.length local calibre_device = self