3.4 KiB
name, description, metadata
| name | description | metadata | ||||||
|---|---|---|---|---|---|---|---|---|
| calibre-plugin-push-4863 | readest-calibre-plugin (#4863) pushes calibre books+metadata to Readest cloud; key protocol facts (OAuth localhost relay, /sync explicit-null carry-over) |
|
apps/readest-calibre-plugin/ implements #4863: calibre GUI plugin pushing selected books + metadata into the Readest cloud, modeled on BookFusion's plugin. MERGED to main via PR #4918 (2026-07-04, merge 6b403d019); packaged in releases as Readest-<version>.calibre-plugin.zip by release.yml's build-calibre-plugin job.
Design decisions and hard-won protocol facts:
-
Identity:
Book.hash= partial MD5 (KOReader algorithm; JS1024 << -2wraps to 0, so offsets are 0, 1024, 4096, ... 1024<<20). metaHash =md5(NFC("title|authors,|ids,")), preferred id scheme uuid > calibre > isbn; Python impl verified byte-identical tojs-md5output. -
OPF embedding + uuid dedup (v2, per maintainer request): metadata IS embedded into a temp copy at upload (
calibre.ebooks.metadata.meta.set_metadata— deterministic for EPUB, writes custom columns ascalibre:user_metadata). Dedup keys: calibre uuid in rowmetadata.identifier(survives byte changes) +metadata.calibreSourceHash= raw library-file partialMD5 (change detection, no local state; v1 rows fall back tobook_hashwhich equals the raw hash). File changed → replace flow: upload new blob, push new row (carry-over) + tombstone old in one /sync POST, best-effort delete old cloud files. Metadata-only edit → row update, no re-upload (embedded OPF goes stale until next file upload). -
POST /sync explicit-nulls absent fields (transformBookToDB) — updates must carry over
uploadedAt,groupId/Name,progress,readingStatus*,coverHashfrom the pulled server row (wire.py::merge_for_push); same lesson as koplugin syncbooks.lua. -
Upload key
Readest/Books/{hash}/{hash}.{ext}; app's{title}.{ext}downloads resolve via download.ts hash+extension fallback. cover.png stores original bytes (app never converts formats, bookService.ts:568), so calibre's cover.jpg bytes upload as-is; coverHash = partialMD5 of those bytes. -
OAuth from a non-app client works:
{supabase}/auth/v1/authorize?provider=X&redirect_to=http://localhost:PORTis whitelisted (readest-app's Flatpak/custom-OAuth production path uses it). Tokens arrive in the URL fragment; serve a page whose JS relayslocation.hashto/callback?...(tauri-plugin-oauth trick). Implemented inoauth.py. -
Pure modules (
api.py,wire.py,oauth.py) are calibre-free;make testruns 56 unittests;make zipbuilds; smoke-test inside calibre withcalibre-debug -cafterfrom calibre.customize.ui import find_plugin(initializes thecalibre_pluginsnamespace). -
Release packaging (PR #4918):
build-calibre-pluginjob in release.yml mirrors the koplugin job; perl-stampsPLUGIN_VERSIONfrom readest-app package.json,make zip→Readest-<version>.calibre-plugin.ziprelease asset. Committed version stays the (0, 1, 0) dev placeholder. -
Pushing workflow files: gh's OAuth token lacks
workflowscope (HTTPS push of .github/workflows/* rejected); SSH push works (transient hangs — retry with ConnectTimeout/ServerAliveInterval).
Related: koplugin-cover-upload, grimmory-native-sync, ci-pr-delivery-and-push