mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
edc2a2ccfc
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.
82 lines
2.5 KiB
Lua
82 lines
2.5 KiB
Lua
--[[--
|
|
Server capability detection for wire features the plugin can only use when the
|
|
server advertises them.
|
|
|
|
Plugin and server versions ship independently, so a new route is selected only
|
|
after the server names it. Detection is cached per server URL and login for the
|
|
plugin session, and re-checked when the server version changes.
|
|
|
|
A downgrade is recorded only for a confirmed answer. Timeouts, 5xx responses
|
|
and authentication failures leave the capability unknown, so a transient
|
|
problem never parks the plugin on the legacy path for the rest of the session.
|
|
]]
|
|
|
|
local Capabilities = {}
|
|
|
|
local cache
|
|
|
|
local function cacheKey(client)
|
|
return tostring(client and client.server_url) .. "|" .. tostring(client and client.username)
|
|
end
|
|
|
|
local function isTransient(err)
|
|
if err == nil then return true end
|
|
if type(err) == "number" then
|
|
-- 4xx is a definitive answer from a server that understood us; 5xx and
|
|
-- transport failures are not.
|
|
return err >= 500
|
|
end
|
|
return true
|
|
end
|
|
|
|
function Capabilities.reset()
|
|
cache = nil
|
|
end
|
|
|
|
-- Returns the cached capability list for this client, or nil when unknown.
|
|
function Capabilities.cached(client)
|
|
if cache and cache.key == cacheKey(client) then return cache.capabilities end
|
|
return nil
|
|
end
|
|
|
|
function Capabilities.remember(client, body)
|
|
local names = {}
|
|
for _, name in ipairs((body or {}).capabilities or {}) do
|
|
if type(name) == "string" then names[name] = true end
|
|
end
|
|
cache = {
|
|
key = cacheKey(client),
|
|
capabilities = names,
|
|
server_version = body and body.serverVersion or nil,
|
|
}
|
|
return names
|
|
end
|
|
|
|
-- Returns true when the feature is advertised, false when the server answered
|
|
-- without it, and nil when the answer is still unknown.
|
|
function Capabilities.supports(client, feature)
|
|
local known = Capabilities.cached(client)
|
|
if known then return known[feature] == true end
|
|
|
|
local body, err = client:getPluginVersion()
|
|
if not body then
|
|
if isTransient(err) then return nil end
|
|
Capabilities.remember(client, {})
|
|
return false
|
|
end
|
|
return Capabilities.remember(client, body)[feature] == true
|
|
end
|
|
|
|
-- Records a confirmed unsupported response observed on the feature's own
|
|
-- route, so a 404 from an older server downgrades immediately.
|
|
function Capabilities.markUnsupported(client, feature)
|
|
local known = Capabilities.cached(client)
|
|
if not known then
|
|
known = {}
|
|
cache = { key = cacheKey(client), capabilities = known }
|
|
end
|
|
known[feature] = false
|
|
end
|
|
|
|
return Capabilities
|