Files
bookorbit/koreader-plugin/bookorbit.koplugin/bookorbit_queue.lua
T
neon edc2a2ccfc feat(reader): make KOReader plugin sync non-blocking and resumable
Lifecycle handlers previously blocked on network work, so closing or suspending a book stalled the
device on slow or offline links. Persist snapshots first and drain them through a lifecycle outbox
with connected-state gates and phase-aware retries, so the UI stays responsive and pending work
survives restarts.

Move persistence behind a state manager with atomic flushes, and chunk sweeps, bulk downloads, and
annotation exchange through a background queue with checkpoints and transfer policies, so large
libraries make progress incrementally instead of restarting from scratch after an interruption.

Server side, add bulk manifest and page stats endpoints with batched queries and catalog query
filtering so the plugin can reconcile many books per round trip rather than one request per book.
2026-07-28 02:06:50 +00:00

113 lines
3.1 KiB
Lua

--[[--
Head-index work queue for the sweep's per-book lists.
`table.remove(queue, 1)` shifts every remaining element, so draining a queue
that way costs O(n^2). A LuaJIT microbenchmark measured 0.51 seconds of pure
shifting for 50,000 items against 0.00003 seconds for a head cursor. The stats,
annotation, state and progress queues all grow with the library, so they
consume through a head index instead.
`#queue` stops being the remaining work once the head moves, so progress lines
read `remaining()`, `done()` and `total()` here rather than the array length.
]]
local BookOrbitQueue = {}
BookOrbitQueue.__index = BookOrbitQueue
-- Consumed slots are reclaimed only once they dominate the array, so a long
-- queue never pays a shift per pop.
local COMPACT_MIN_HEAD = 256
function BookOrbitQueue.new(items)
local self = setmetatable({}, BookOrbitQueue)
self.items = items or {}
self.head = 1
self.tail = #self.items
self.added = self.tail
return self
end
function BookOrbitQueue:push(item)
self.tail = self.tail + 1
self.items[self.tail] = item
self.added = self.added + 1
end
function BookOrbitQueue:isEmpty()
return self.head > self.tail
end
function BookOrbitQueue:remaining()
local left = self.tail - self.head + 1
return left > 0 and left or 0
end
-- Everything ever queued. Re-queued entries are retries of work already
-- counted, so they do not move this.
function BookOrbitQueue:total()
return self.added
end
function BookOrbitQueue:done()
return self.added - self:remaining()
end
function BookOrbitQueue:peek()
return self.items[self.head]
end
local function compact(self)
if self.head <= COMPACT_MIN_HEAD then return end
if (self.head - 1) * 2 <= self.tail then return end
local items, target = self.items, 1
for index = self.head, self.tail do
items[target] = items[index]
items[index] = nil
target = target + 1
end
self.tail = target - 1
self.head = 1
end
function BookOrbitQueue:pop()
if self.head > self.tail then return nil end
local item = self.items[self.head]
self.items[self.head] = nil
self.head = self.head + 1
compact(self)
return item
end
-- Returns a just-popped entry to the front, used by the legacy annotation
-- fallback when it re-queues the in-flight book before switching endpoints.
-- The vacated head slot is reused unless a compaction already reclaimed it.
function BookOrbitQueue:requeue(item)
if self.head > 1 then
self.head = self.head - 1
self.items[self.head] = item
return
end
table.insert(self.items, 1, item)
self.tail = self.tail + 1
end
-- Iterates the entries that are still queued. Anything walking the queue has
-- to start at the head; index 1 is a consumed slot once the head has moved.
function BookOrbitQueue:iter()
local index = self.head - 1
return function()
index = index + 1
if index > self.tail then return nil end
return self.items[index]
end
end
function BookOrbitQueue:clear()
self.items = {}
self.head = 1
self.tail = 0
self.added = 0
end
return BookOrbitQueue