Resolve OPF and Dublin Core namespaces so valid prefixed metadata retains
book language and avoids incorrect English dictionary lookups.
Preserve element order and identifiers, and cover reader metadata and
narration with unit and HTTP regression tests.
Closes#1789
Library access roles had no effect on metadata editing: only the global Edit metadata permission
was checked, so a Viewer could edit every library they could see and an Editor could not be
limited to one library. Require Editor or Owner on the book's library for every metadata write,
including bulk edits, locks, covers, file renames, entity manager changes and author edits, and
hide the matching actions in the web client.
Keep existing access levels when an admin saves a user's library list. The old replace reset every
row to Viewer, which would now silently revoke editing on each user save.
Existing grants are Viewer, so after upgrading, admins must set Editor on each library where a
non-superuser should keep editing metadata. The released iOS app already gates editing on Editor.
Closes#1778
The authorization matrix fails for any live route that is neither inventoried nor in its frozen
gap baseline, and GET /server-identity arrived without an entry. Record it as public with no
permission or library access, which is exactly what the route serves.
The iOS app lets an account reach one server through several addresses, such as a home network
IP and a public name. Before it sends credentials to an alternative address it needs to know the
address leads to the same installation, so expose a public, credential-free GET /server-identity
returning a persisted UUID, and add the same optional instanceId to app-info with the
server-connection-identity feature flag.
The UUID lives in a singleton server_identity row shared by every replica, created on first use
and cached per process. An unreadable identity omits instanceId instead of failing app-info,
which clients rely on for feature detection. Existing routes and bodies are unchanged and the
web client does not call the new route; the iOS app does.
The migration uses IF NOT EXISTS because an unreleased build created the same table as 0109.
Fix the server and web gaps found while porting the PDF and comic
readers and offline downloads to Android, checked against the iOS app:
- KOReader: answer a pull for a PDF or comic with the page number when a
BookOrbit client wrote last, instead of a percentage with no page
- comics: key the zip offset, RAR and CB7 caches by file id and
modification time, so pages are not served from stale offsets after a
metadata write-back rewrites the archive
- comics: answer an unreadable archive with 422 rather than a page count
of 0, and extract CB7 with its folders so same-named pages in
different folders no longer overwrite each other
- book files: send an ETag and Last-Modified on serve and download and
honour If-None-Match and If-Range, clamp a range that runs past the
end, answer suffix ranges with 206, and honour Range on download,
which advertised it but sent the whole file
- book files: leave raw file routes uncompressed, so FB2 and unknown
formats keep their Content-Length
- progress: reject a fractional or negative page number with 400 rather
than failing with 500 at the database
- types: declare the comic page-count response shared by the web and
the apps
- web: open a percentage-only PDF position at its estimated page, and
flush pending PDF and comic progress before the reading session ends
Preserve reset-hold notices when recovery requests fail and prevent
stale responses from replacing another book's progress. Refresh holds
after permission changes and rereads, and cover recovery and session
deletion with regression tests.
Ref #1355
Deleting a KOReader session left its page events stored, so the next upload within the
session gap re-derived it. Record the deleted span in koreader_session_tombstones and skip
those events during derivation. Show the reset-hold notice on the Reading Log as well,
since Reset is reachable there and a held device otherwise reads as unexplained progress.
Refs #1355
Fold six tabs into Recipients, Mail servers, Templates and History, where only problems get
colour and one star sets each default through email_preferences. Replace the send dialog with
a sheet that preselects the default recipient and follows the send. Server additions are
optional fields and routes, so released iOS builds keep working.
A cached Hardcover match outranked a corrected Hardcover ID or ISBN, so progress kept going to the
wrong book. Record the identifiers each link came from and re-check it when they change, honour the
metadata edition on a match, and release the old book's read links so they are never reused.
Closes#1488
Keep disabled Prowlarr limits from being reapplied through tracker metadata,
cached releases, or stale source rows after a failed synchronization.
Cover policy changes, synchronization, both torrent transports, client defaults,
and the PostgreSQL join with regression tests.
Closes#1625
Audiobookshelf reports a progress row's creation time as its start, while
clients set the finish date freely, so a backfilled finish can predate it.
One such row broke user_book_status's date check and the whole status batch
with it. Keep the finish date and drop the start, and store the database's
reason on a failed run instead of the failed SQL.
Closes#1678
The player follows each track's stored sort order, which overlapping
folder scans, Add File, and imports joining a book could leave out of
step with the file names the Files tab shows. Realign a book's tracks
after each of those writes, under the book row lock.
Closes#1681
Book Dock finalize, which also files book requests, left each audio file's
duration empty until the next library scan. The player derives progress only
from those, so a filed audiobook stayed at 0% and never reached Continue
Listening. Finalize now measures them once metadata is applied.
Refs #1757
Map <Genre> to genres and <Tags> to tags instead of using tags as a genre
fallback, which dropped them whenever a Genre was present. Read the
ComicBookInfo genre field so those comics keep their genres.
Fixes#1572
Add original and text-only EPUB delivery choices across Kobo, KOReader,
OPDS, web downloads and email while keeping source files unchanged.
Persist server defaults, device overrides and captured artifact identities
so retries and guided replacement retain the selected rendition.
Preserve saved progress across first opens, replacement resets and stale
updates, and project annotations against the copy delivered to each device.
Handle XML resource references, large inodes, retry backoff and background
worker state correctly. Consolidate the schema changes into one migration
and retain released-client request and response compatibility.
Verify with the full server and client checks, delivery and sync HTTP suites,
independent code reviews, and live KOReader download/settings/reader proof.
Fixes#1496Closes#1603Closes#1714Fixes#1723
Ref #1633
Ref #1508
Ref #1621
Ref #1136
Repeated searches and refreshes asked every provider again, which is slow and runs into provider
rate limits. Keep each provider's answer in Postgres, shared by all users, and reuse it until an
admin-set lifetime runs out. Search screens mark saved results and offer a fresh search, and
explicit refreshes stay live, falling back to a saved answer only when a provider fails.
Empty answers are only kept for API providers that report failures reliably, and a cache-only
request never calls a provider in cooldown. Book Dock's manual refetch stays live-only because
the dock has nowhere to report a fallback. Released iOS builds keep working: every new request
field is optional and newer clients gate it on the metadata-search-cache server feature.
Keep books and comic archives under the existing ebook request kind so searches, ownership checks,
format preferences and release profiles agree without changing the API contract. Cover the combined
formats and library access rules with unit and end-to-end tests.
Ref #1748
Lead fetches with the primary file, give Book Dock its library's rules and read-along media,
default audiobook fields to sources that carry them, and flag providers that never fill a field.
Joining a format now fills gaps only and respects locks; a move picks its primary as a scan would.
Link two ordinary requests as companions, filed into one book, with a Both search,
grouped list rows, a tabbed release picker and a book-page request for the missing
format. Retire comic requests, keeping old rows readable. File Book Dock covers by the
field rules with a shape check, and re-pick a moved book's primary by its new library.
Use file identities for root-level imports and renames to match scanner
grouping and prevent unrelated books from sharing metadata.
Cover repeated scans, metadata write-back, and stable reading and file
identities with regression tests.
Fixes#1599
Return matching item counts with create and update responses so the sidebar
can display the saved scope count without requiring a page reload.
Reuse the listing count logic and verify visibility and shared sidebar state.
Carry ISBN-10 and ISBN-13 independently from accepted providers through
Auto-fill previews, saves and background refreshes. Preserve existing
identifiers and field locks, validate provider checksums and avoid
combining different editions.
Classify embedded ISBNs correctly and support ISBN-10 fallback in
search, Book Dock confidence and file naming.
The walker sorted a book's files by file name, so tracks in disc folders that reuse names, such as
CD 1/01.mp3 and CD 2/01.mp3, interleaved in playback, merged chapters, and written track numbers.
Order them by their whole path instead, everywhere a track order is chosen.
A rescan that only reorders a book's existing tracks now moves every audio bookmark and the
listening progress percentage to the same moment of the same track, and rebuilds merged chapters.
Disc folders are never skipped by incremental scans, so existing libraries are fixed by their next
scan.
Also inventory the notebook and dashboard highlights routes in the authorization matrix, and pin the
app locale in two date tests that depended on the machine's locale.
Books often carry several files, such as an EPUB beside a PDF or an audiobook split into tracks.
Write-back reached only the primary file and the audio tracks, so the other formats kept stale
metadata.
Add an opt-in per-library mode that writes every content file of a book. Target selection,
capability reporting and the Files tab now share one role-aware selector, covers resolve per
medium, and results report per-format counts. Read-along EPUBs get their own toggle, off by
default, and their own size limit of 1000 MB, so they no longer follow the EPUB settings.
Live testing on copies of real libraries surfaced write-path bugs, fixed here as well:
- tag-only writes to chaptered M4B files failed because FFmpeg copied the chapter data track
- an undecodable cover failed every file instead of falling back to metadata only
- failure reasons stored the FFmpeg command line and absolute paths; they are now sanitized
- size limits used the stored size, so an unknown size bypassed them; writes now check the disk
- atomic replace dropped the file mode and ownership
- the EPUB temp name failed on long file names and could clash with a user file
- tracks in disc folders got duplicate titles
- a PDF worker that exited without a result hung its write forever; it now fails and times out
- null on a NOT NULL library setting returned 500 instead of 400
Generates migrations 0104 and 0105. Every new API field is optional, and released iOS builds
decode none of them.
Ref #694
Refresh dashboard and statistics caches immediately after reading history,
projection, reset, import, and shared deletion changes. Coalesce bulk
notifications while keeping committed changes visible to concurrent reads,
and isolate listener failures from successful writes.
Skip unchanged Hardcover attempt updates and protect cache generations
against stale in-flight loads. Cover date edits, projection-only resumes,
rollback, bulk failures, user scope, and cache races with regression tests.
Ref #1695
Register the identity of delivered bytes so Kobo conversions and audio
removal continue matching their source books in KOReader. Keep download
registration recoverable without allowing database lock waits to stall it.
Preserve manual corrections across cached device identities, make review
semantics explicit, and keep cache invalidation bounded for large libraries.
Add unit, database, lifecycle and scale regressions with one generated migration.
Closes#1697
Align collection visibility across OPDS and KOReader with the web catalog.
Count only books the viewer can access in one aggregate query, and cover
public access, private revocation, and filtered counts with regression tests.
Closes#1701
Allow native KEPUB files to exchange precise reading progress, bookmarks
and highlights with Kobo while preserving sync permissions and settings.
Use the original KEPUB archive for Kobo spans and project its added
wrappers away for shared EPUB CFI and KOReader XPointer positions.
Preserve existing EPUB conversion behavior and API compatibility with
released iOS clients.
Cover native KEPUB context, canonical positions and two-way exchange with
unit tests and paired EPUB/KEPUB API E2E scenarios.
Ref bookorbit/bookorbit-ios-support#36
In folder-as-book libraries a pattern that files several books under one folder either failed on
the folder_path unique index or silently merged a different book into the folder's owner, deleting
its reading history. Check the folder owner on every rename path and merge only when both books
share a title and primary author; otherwise skip with a clear reason. The bulk preview now reports
these as collisions.
Fixes#1682
Add read-only /notebook routes that list highlights, journal entries, bookmarks and reviews
across every book a reader can open, as one cursor-paged stream with per-kind counts, a By Book
listing, a daily review deck, a seeded review shuffle, On this day and a library-wide trash. Each
kind is read through its own index-ordered arm cut at the page size before a UNION ALL merge, so a
deep page costs what the first does, and book labels travel once per page in a sidecar map.
Scope follows library access and content filters. Everything is additive and advertised as the
notebook-hub app-info feature. The iOS app calls these routes and the web client does not yet, so
they look unused from the client alone. No migration: the extra indexes for the edited sort and
search wait until the chapter-editor migrations land.
Add per-book journal entries keyed by a client id, a star toggle that does not bump the annotation
version pushed to devices, a per-user colour legend, and an edit route so bookmark title and note
changes reach the server. All of it is additive and advertised through app-info features, so iOS
sends new fields only to servers that support them. Also sort annotations by numeric CFI steps
instead of comparing CFI strings.
Convert complete source CFIs on the server to avoid chapter-start resets
and browser-generated locators that disagree with CREngine. Preserve
engine whitespace, Unicode offsets, element anchors and long text nodes.
Handle prefixed OPF namespaces and retry outdated annotation conversions.
Closes#1621Closes#1288
Return saved organization and automation settings in the user-scoped library list so
non-superuser summaries reflect the saved configuration.
Cover enabled and disabled settings, library isolation, and permission denials with
API regression tests.
Closes#1664
Keep dashboard layouts browser-local by default and let users opt in to account storage.
Group library selection and shelf sync in one dropdown, preserve legacy client settings,
and block sync changes for demo-restricted accounts.
Fixes#1646
The collapsed order by had no metadataScore case, so the sort was dropped
and the list fell back to title order. Order by metadata_score there and
select it in the collapsed jump-bucket query that shares the order by.
Fixes#1638
Return a user-scoped highlights shelf while preserving the existing single-highlight endpoint. Align calendar dashboard figures with each reader's timezone and completed attempts.
The scroller route returns a DashboardScrollerResponse of books and total, but this spec still
parsed the body as a bare array, so it failed before reaching its assertions.
Allow 0.05% adjustments while preserving existing integer settings and the 98% default. Keep finish detection stable when progress is stored as PostgreSQL real.
* fix(metadata): write finalized metadata back to the imported file
Finalizing a Book Dock file only moved the bytes, so a library with file
writes enabled kept the metadata the file was uploaded with rather than
the metadata the dock settled on. Schedule a write-back for every book a
finalize produces once all of them have their metadata, attributed to
the uploader.
Fixes#1385
* fix(metadata): schedule dock file writes after cleanup
Prevent a failed Book Dock cleanup from leaving a pending file write. Keep the integration assertion synchronized with the debounced writer and restore the missing directory stability mock for end-to-end coverage.
---------
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Match subtitle metadata in book searches and index it for large libraries. Add regression coverage for accent matching and library visibility.
+ add open collective
Closes#1552
A file filed into a book that already existed was added without re-ranking, so the
book kept whichever file created it: an ebook that arrived first stayed primary in a
library that ranks audiobooks first. Imports and uploads now re-rank with the
library's priority, keep the current primary on a tie, and a rolled-back finalize
restores the primary it replaced.
Keep one cover slot per medium and show the one each surface needs: the
library's shape for the face, square art on audio screens, the ebook slot
for Kobo and OPDS. Legacy covers convert on upgrade or on first read, and a
write that names no slot keeps working for released iOS builds. Also read
the dev client origin from one constant.
Run the dev API on 6262 and Vite on 6263 with strictPort; production
defaults stay on 3000. Define the dev client origin once and read the
typed appConfig instead of repeating its fallback in every gateway.
Carry positions between sibling EPUBs even without an audiobook, and keep
narration markers on the read-along file only. Point the KOReader book panel
and hold release at the copy devices sync, and label EPUB-only sync in the
read-aloud panel.
Fixes#1508
Track colliding KOReader statistics rows independently and require verified explicit targets before upload. Keep unresolved history pending with a visible recovery path.
Fixes#1457
Recompute the open file digest and recover previously verified matches without guessing between duplicate files. Preserve stranded statistics across lifecycle retries and advertise plugin version 1.5.3 so installed devices can update.
Fixes#1497
The activity overview derived completions from reading sessions that reached 99 percent, while the
dashboard's reading-goal widget counted completed reading attempts. The two disagreed on iOS, where
Home reads the widget and the Annual Goal card reads the overview: a book marked read by hand, one
finished on Kobo or KOReader, an audiobook whose last session stops short of the end, and a re-read
collapsed into its first finish were all invisible to the session heuristic.
Commit 3de365a0 already made completed attempts canonical for the web timeline, goal trajectory and
completion latency. The activity-overview route arrived later with its own session-based query and
reintroduced the divergence on the one surface the web never calls.
Drop the timezone conversion with it. endedOn is a calendar date rather than an instant, so it has
no zone to convert and AT TIME ZONE would only shift buckets across year boundaries.
Fixes#1474
Replace the hardcoded English in the TTS and narration components with
message keys, label every icon-only control, and add settings.common.nav.tts,
which the settings sidebar rendered as a raw key.
Stop requiring PODCAST_ENCRYPTION_KEY while APP_FEATURES.podcasts is off, and
skip podcast routes and permissions in the authorization matrix for the same
reason.
Pass the direct-download settle() helper the SETTLE_MS budget the file already
defines: vi.waitFor's one-second default cannot cover six DNS-resolved redirect
hops on a cold CI resolver.
* feat(settings): add SABnzbd download client
Support API-key authentication, NZB uploads, queue monitoring, reconciliation, and completed-download imports through SABnzbd.
Expose the client in request settings and validate its required category and credentials.
Ref #1312
* feat(settings): add Prowlarr indexer management
Manage synchronized torrent and Usenet sources through a shared Prowlarr connection.
Ref #1312
* fix(settings): correct SABnzbd adoption and managed search retries
Address four defects found in review of the Prowlarr and SABnzbd work.
SABnzbd keeps a failed job in its history until somebody clears it, and BookOrbit removes a client
download only on an explicit action. Because the client key is a stable digest, re-grabbing the same
release adopted that dead history entry, never uploaded the NZB, and failed instantly and forever.
Adopt only an entry that is not already failed; a queue entry still wins, so a fresh upload reports
correctly.
The category-expansion retry reused the title-and-author query, making it strictly narrower on text
than the title-only attempt that had just returned nothing. A release matching only a title-only
search with expanded categories was therefore unreachable. Drop the author there so each rung of the
ladder is genuinely broader than the last.
Changing the download client type through the create form's select bypassed the picker's handler, so
a username typed for qBittorrent was still sent when SABnzbd was chosen. The server stores it, the
API-key form hides it, and it can no longer be edited away. Watch the credential kind instead, so
both controls clear it.
Finally, a swallowed managed-release refresh failure left nothing behind at all; give it its own
fail log while still reporting the original refusal to the caller.
* fix(requests): cast managed search health flags to boolean
The bulk search-health update bound each case branch as an untyped
parameter, so Postgres could not resolve it against the boolean column.
Cast every branch explicitly and assert the cast in the repository test.
* fix(requests): close review gaps in Prowlarr and SABnzbd support
Inventory the eight indexer-manager routes so the authorization matrix
probes them, resolve a managed indexer without reading its manager's
whole source list, keep an unreadable credential out of the test-failed
mapping, translate manager error codes, and show per-source search
health. Documents Prowlarr connections in docs/BOOK_REQUESTS.md.
* i18n(client): keep the retired sources empty-state key
Dropping bodyOneWay from en.json forced pruning it from five Crowdin
target catalogs, which the localization policy check rejects outright.
Leave the now-unused key in place; nothing validates unreferenced
English source keys, so Crowdin can retire it on its own schedule.
* db: regenerate the SABnzbd and Prowlarr migration onto main
Rebasing collided with main's own 0091 and 0092, which left this branch's
two migrations on disk with no journal entry referencing them. Drop both
and regenerate a single migration on main's lineage; the three affected
tables are schema-identical to the pre-rebase snapshot.
Sort groups by reclaimable space, pre-pick a keeper with a rule that states
its reasoning, and expand any row into a field-by-field comparison that shows
only what differs. Persist "not duplicates" so a judgement survives a rescan,
and review every copy before deleting. Reading progress no longer renders 0%.