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