From dea7903e1042ea14fc73b72d020d7239e238b7c3 Mon Sep 17 00:00:00 2001 From: vavallee Date: Sun, 11 Oct 2026 21:26:58 -0300 Subject: [PATCH 1/5] docs: bring documentation up to date with what merged since v1.41.0 Provider lists in the README, the roadmap Delivered and Next up sections, the In Library meaning for partly owned dual format books, ratings, the Authors list have / total count and the author page sort and filters, the series field in the book edit table, the availableBookCount and rating fields, the rating sort values and the primary provider values in the API reference, and the one time dedup key recompute on first boot. Documents #3125, #3095, #3088, #3134, #3090, #3127, #3150, #3158, #3116, #2915 and #2994. No new environment variables, dependencies or breaking behaviour shipped, so DEPLOYMENT.md env tables, THIRD_PARTY_LICENSES.md and docs/upgrade-v1.md are unchanged. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee --- README.md | 8 ++++---- docs/API.md | 23 +++++++++++++++++++++++ docs/DEPLOYMENT.md | 9 +++++++++ docs/Metadata-Editing-Wiki.md | 1 + docs/ROADMAP.md | 8 +++++--- docs/User-Guide-Wiki.md | 30 +++++++++++++++++++++++++----- 6 files changed, 67 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 86c624e23..e091fe7a9 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Bindery is built so that can't happen to you: -- **No single point of failure for metadata.** OpenLibrary leads, with Google Books, Hardcover, DNB, Audnex, and Audible behind it — all documented public APIs, zero scraping. If one source degrades, the others carry the library. +- **No single point of failure for metadata.** OpenLibrary leads, with Google Books, Hardcover, DNB, Audnex, and Audible behind it, and Nasjonalbiblioteket and the BnF available as opt in primaries for Norwegian and French libraries. All documented public APIs, zero scraping. If one source degrades, the others carry the library. - **Bring your dead Readarr install with you.** Import `readarr.db` and your authors, indexers, download clients, and blocklist come across in one step. No re-entry, no CSV gymnastics. - **Boring to run, by design.** A single Go binary with the UI embedded, SQLite, no external database, no sidecars, no nginx. Distroless image, Helm chart, and ARM builds down to a Pi Zero. @@ -118,7 +118,7 @@ Clean-room Go rewrite, modern React UI, MIT-licensed, actively developed. - Author monitoring via OpenLibrary's author-works endpoint, configurable author monitor modes, all, future, latest or none as the install default plus a per-author mode that follows only the series you pick, per-book monitor toggles, and a `wanted → imported` workflow with live download progress on the Queue page. - Unattended release discovery: monitored authors are checked for new books on a schedule, so a followed author's next book joins the library without a click. It ships off; turn it on in **Settings, General, New release discovery** by picking Daily, Weekly or Monthly. Opt an author out with **Monitor new items: Don't add them**. - Dual-format books — each title holds an ebook *and* an audiobook in independent slots, with separate search, grab, and import pipelines, and the audiobook side moves multi-part `.m4b` / `.mp3` folders as one unit. -- Series support with position tracking and search by series title, edition tracking (format / ISBN / publisher / page count), Calendar view of upcoming releases, and multiple library roots. +- Series support with position tracking and search by series title, a book's series and position fixable from the book itself (a book you take out of a series stays out on refresh), edition tracking (format / ISBN / publisher / page count), Calendar view of upcoming releases, and multiple library roots. - Library scan matching in five passes: exact ASIN, then a backwards `Author - Title` filename whose folder author the catalogue already knows, then the filename's title, then the book folder's title, and finally series name plus position. Every title pass is one fuzzy comparison that has to agree on the author as well. Honours librarian sort-suffix form (`Title, The`) and series-annotated filenames (`[Mistborn, Book 1]`). - **Library adoption**: whatever the scan could not match waits on the Import page as one row per book (an audiobook folder or disc set is one row), with suggested matches (one click Confirm only for a strong match), one "Add author" decision per missing author, and Undo. Files are registered where they are, never moved. - Author aliases (`RR Haywood` / `R.R. Haywood` / `R R Haywood` merge into one canonical row), and metadata re-bind to correct a wrong match without delete-and-re-add. On a book’s **File → Re-bind** dialog, search configured metadata providers and select an OpenLibrary work or Hardcover book, or enter an exact provider ID and press Enter. Results already in the library are marked and link to the existing book; records belonging to another library book cannot be selected. Each result’s **Links** menu opens its upstream page when available; OpenLibrary editions can be viewed but require a work ID for re-binding. Re-binding updates metadata and series without moving files; author mismatches require confirmation. @@ -179,7 +179,7 @@ Cover images are fetched and cached server-side under `/image-cache/` ( - Five entries in the top bar: **Library** (Authors, Books, Series), **Activity** (Wanted, Queue, History, and Requests for an admin), **Import**, **Calendar** and **Discover**. A group opens on its first page and repeats the rest as tabs above the content, and every page keeps the address it always had. - Library search in the header over your authors, books and series, with a last row that hands a miss to a single **Add to library** dialog for authors and books (title, author, ISBN or ASIN). - Light / dark themes (respecting `prefers-color-scheme` first paint), grid / table view toggles, mobile-friendly responsive layout, hamburger nav, agenda-style mobile Calendar. -- Full pagination, search, filter, and sort on every list page; preferences persist to `localStorage`. +- Full pagination, search, filter, and sort on every list page, including sorting an author's books by title, date or added and the Books list by community rating; the Authors list shows how many of each author's books you have; preferences persist to `localStorage`. - Previous/Next navigation between authors on the author detail page, stepping through the list page you came from. - Previous/Next navigation between books, stepping through the list page you came from (Books, an author's own book list, or Wanted). - 10 languages: English, French, German, Dutch, Spanish, Filipino (Tagalog), Indonesian, Korean, Norwegian (Bokmål) and Swedish. Auto-detected from the browser, override in Settings. @@ -255,7 +255,7 @@ The full reference (path remapping, API-key seeding, telemetry, trusted-proxy, r | **Usenet clients** | SABnzbd, NZBGet | | **Torrent clients** | qBittorrent, Transmission, Deluge, rTorrent / ruTorrent (XML-RPC over HTTP or SCGI) | | **Indexers** | Newznab (NZBGeek, NZBFinder, NZBPlanet, DrunkenSlug, …), Torznab (Prowlarr, Jackett, direct endpoints), with per-indexer category overrides | -| **Metadata sources** | OpenLibrary, Google Books, Hardcover, DNB, Audnex, Audible | +| **Metadata sources** | OpenLibrary, Google Books, Hardcover, DNB, Nasjonalbiblioteket (opt in), BnF (opt in), Audnex, Audible | | **Import lists** | Hardcover lists, synced on a configurable interval | | **Library tools** | Calibre (`calibredb`, Bindery Bridge plugin, `metadata.db` import), Audiobookshelf (import and scan trigger), Grimmory (preview), post-import drop folder for Calibre-Web-Automated and similar ([guide](docs/Calibre-Integration-Wiki.md)) | | **Notifications** | Generic webhooks — pipe to Apprise / ntfy / Home Assistant / Slack / Discord | diff --git a/docs/API.md b/docs/API.md index 0bf106f94..ddbf04c6a 100644 --- a/docs/API.md +++ b/docs/API.md @@ -96,6 +96,12 @@ GET /api/v1/author/{id}/aliases list merged-in alias rows POST /api/v1/author/{id}/merge merge another author into this one ``` +Each author in `GET /api/v1/author` carries `statistics.bookCount` (the author's +books, scoped to the caller like `GET /book`) and `statistics.availableBookCount` +(#3092), the subset in the library by the same rule as `GET /book?status=imported`: +a book with at least one tracked file, including a `wanted` book that already holds +one of its formats. `wantedBookCount` is not filled on the list. + `POST /api/v1/author/book` answers **409 Conflict** when `foreignBookId` (or the canonical id it resolves to on the ISBN path) already matches a book in the requesting user's library (#1227). "In the library" is scoped exactly like @@ -346,6 +352,12 @@ GET /api/v1/book/{id}/file download the imported file (au GET /api/v1/book/{id}/calibre where the book stands in the Calibre delivery queue (see Calibre below) ``` +Each book carries `averageRating` (community rating, 0 when the provider has none) +and `ratingsCount` (#3124). `GET /book?sort=` also accepts `rating-desc` and +`rating-asc`; unrated books sort last in both directions, and `rating-desc` breaks +ties by `ratingsCount`. OpenLibrary results of `GET /search/book` carry the +same two fields. + ### Series ``` @@ -424,6 +436,12 @@ client can offer "open" for those rows and "add" for the rest. The lookup is owner scoped: another user's copy does not stamp. If the library lookup itself fails the search still succeeds, unstamped. +An author in `/search/author` carries the provider's work count as +`statistics.bookCount` when one is known (#3126). Hardcover supplies it as well as +OpenLibrary and Nasjonalbiblioteket, and when several providers return the same +author the surviving record keeps the largest count reported, so the count is not +lost when a provider that reports none wins the merge. + ```json [ { "foreignBookId": "OL27448W", "title": "The Hobbit", "libraryBookId": 42 }, @@ -1223,6 +1241,11 @@ unset key: | `author.default_monitor_mode` | add author dialog | | `author.default_monitor_latest_count` | add author dialog | +`metadata.primary_provider` accepts `openlibrary` (the default), `dnb`, `nb` +(Nasjonalbiblioteket), `bnf` (Bibliothèque nationale de France, #3144) and +`hardcover`. `nb` and `bnf` are queried only while selected: an instance that +never selects them never contacts either library. + Requests authenticated with the API key are treated as admin, so a script or a third party client using the key still reads every non secret setting. A client signed in as a `user` account that read other keys should switch to the API diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 663de5f42..18c4220d0 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -680,6 +680,15 @@ checks the live schema and restores that additive column automatically. Take a normal SQLite backup before upgrading; no manual SQL is required for this specific repair. +### Duplicate key recompute (v1.42.0) + +The key Bindery stores in `books.dedup_key` to recognise the same book twice now +folds a title filed with its article last (`Trace of Death, A` and `A Trace of +Death`, #3122), so it is rewritten for every book once, on the first start after +the upgrade. It runs during startup, before Bindery begins serving, and adds no schema +migration; on a development machine 100,000 books took about 1.4 seconds. Rows that now share a key are neither merged nor removed: they +show up together in **Review duplicates** for you to decide. + ### Calibre library covers repair Versions before the #2564 fix stored each Calibre-imported book's cover as the library's absolute path in `editions.image_url`, which nothing could serve. On every start Bindery now runs a background pass over such rows: each readable `cover.jpg` is copied into `covers/` under `BINDERY_DATA_DIR` and the edition (and the book, when it has no provider cover) is repointed at the copy. No schema migration is involved. The Calibre library must be mounted at the path recorded in the rows for the pass to read the files; rows it cannot read are left untouched and retried on the next start, and a library import rewrites them as well. Progress is logged as `calibre cover repair finished` at `info`. diff --git a/docs/Metadata-Editing-Wiki.md b/docs/Metadata-Editing-Wiki.md index 5c3e56f41..c3a295949 100644 --- a/docs/Metadata-Editing-Wiki.md +++ b/docs/Metadata-Editing-Wiki.md @@ -14,6 +14,7 @@ Open a book's detail page and click **Edit**. You can change: | Genres | Comma-separated; the first genre drives the `{Genre}` naming token | | Language | Language code, e.g. `en`, `it` — drives the `{Lang}` naming token | | Release date | `YYYY-MM-DD`, or clear it entirely | +| Series | The author's series, with a position; admin only. See [Fixing one book's series](Hardcover-Series-Wiki.md#fixing-one-books-series) | Every field you edit is **locked**: the nightly metadata refresh, the author-works sync, and Audiobookshelf/Calibre re-imports keep your value diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 4876fdb2b..94b4f31bc 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -16,7 +16,7 @@ Real candidates, not commitments, roughly in order of how much they'd change the - **Series from file path** ([#1430](https://github.com/vavallee/bindery/issues/1430)): the last open piece of bringing an existing library in. Adopting what a library scan could not match, the Manual Import wizard, creating a book from an unmatched file, and bulk folder import have all shipped (see **Delivered**). Importing an existing library is still the single biggest source of support threads. -- **Series discovery** ([#2523](https://github.com/vavallee/bindery/issues/2523)): a monitored series finds its own new books. Waits on telling a new work from a new edition ([#2524](https://github.com/vavallee/bindery/issues/2524)). +- **Series discovery** ([#2523](https://github.com/vavallee/bindery/issues/2523)): a monitored series finds its own new books. Waits on telling a new work from a new edition ([#2524](https://github.com/vavallee/bindery/issues/2524)); the first piece, not listing the split edition parts of a book you own as missing, shipped in v1.42.0, and the rest of that issue is still open. - **Merge books / multiple editions** ([#1358](https://github.com/vavallee/bindery/issues/1358)) — group alternate and non-English editions under one book instead of minting duplicates. Real design work; matters most to non-English users. @@ -36,7 +36,9 @@ Condensed; see [CHANGELOG.md](../CHANGELOG.md) for the full history. The Calibre-watched drop folder ([#64](https://github.com/vavallee/bindery/issues/64)) shipped in v0.9.0 and was **removed in v0.17.0**: it depended on the Calibre GUI running with its auto-add watcher active, which never holds in a headless container, so books silently timed out. The `calibredb` mode reaches the same result needing only a shared library volume. -**Search and metadata** — Direct title/keyword search page ([#85](https://github.com/vavallee/bindery/issues/85), [#267](https://github.com/vavallee/bindery/issues/267), v0.20.0). Split ebook/audiobook search results ([#333](https://github.com/vavallee/bindery/issues/333), v1.2.x). Non-English metadata: per-author `allowed_languages` filtering during ingestion ([#14](https://github.com/vavallee/bindery/issues/14), v0.6.0), language propagation into Prowlarr/Jackett queries (v0.12.0), language tags in result views, and edition-level language from Hardcover and Google Books. DNB (Deutsche Nationalbibliothek) provider over the public SRU/MARC21 endpoint, no API key ([#67](https://github.com/vavallee/bindery/issues/67), v0.x, deepened through v1.27.0). +**Search and metadata** — Direct title/keyword search page ([#85](https://github.com/vavallee/bindery/issues/85), [#267](https://github.com/vavallee/bindery/issues/267), v0.20.0). Split ebook/audiobook search results ([#333](https://github.com/vavallee/bindery/issues/333), v1.2.x). Non-English metadata: per-author `allowed_languages` filtering during ingestion ([#14](https://github.com/vavallee/bindery/issues/14), v0.6.0), language propagation into Prowlarr/Jackett queries (v0.12.0), language tags in result views, and edition-level language from Hardcover and Google Books. DNB (Deutsche Nationalbibliothek) provider over the public SRU/MARC21 endpoint, no API key ([#67](https://github.com/vavallee/bindery/issues/67), v0.x, deepened through v1.27.0). Nasjonalbiblioteket (National Library of Norway) as an opt in primary provider ([#2979](https://github.com/vavallee/bindery/issues/2979), v1.41.0) and the Bibliothèque nationale de France as another ([#3144](https://github.com/vavallee/bindery/issues/3144), v1.42.0), both queried only while selected. Covers taken from a book's own file when no provider has one ([#2980](https://github.com/vavallee/bindery/issues/2980), v1.42.0), and no cover taken from a translation's ISBN ([#3018](https://github.com/vavallee/bindery/issues/3018), v1.42.0). + +**Series**: merging several series into one ([#2554](https://github.com/vavallee/bindery/issues/2554), v1.41.0), fixing a single book's series and position from the book with removals that stay removed ([#2554](https://github.com/vavallee/bindery/issues/2554), v1.42.0), and split edition rows of a book you own counted as covered instead of missing (part of [#2524](https://github.com/vavallee/bindery/issues/2524), v1.42.0; the issue stays open, see Series discovery under **Next up**). **Acquisition and monitoring**: One search-first **Add to library** dialog for authors and books, with library matches marked in place, plus a header search over your own catalogue that hands misses to it ([#1227](https://github.com/vavallee/bindery/issues/1227), [#2551](https://github.com/vavallee/bindery/issues/2551), v1.36.0). Adding one book no longer imports the author's back catalogue, and an author refresh respects monitor mode ([#1816](https://github.com/vavallee/bindery/issues/1816), [#1815](https://github.com/vavallee/bindery/issues/1815), v1.31.0). Hardcover list sync runs as a background job instead of inside the request, with a configurable interval ([#1854](https://github.com/vavallee/bindery/issues/1854), [#1848](https://github.com/vavallee/bindery/issues/1848), v1.31.0). @@ -50,7 +52,7 @@ The Calibre-watched drop folder ([#64](https://github.com/vavallee/bindery/issue **Storage and privacy** — Import modes move / copy / hardlink ([#54](https://github.com/vavallee/bindery/issues/54), v0.12.0). Configurable default root folder ([#332](https://github.com/vavallee/bindery/issues/332), v1.2.0). Server-side cover-image proxy and cache so the browser never contacts third-party image hosts ([#112](https://github.com/vavallee/bindery/issues/112), ~v0.16.0). Persistent structured log store in SQLite, queryable and retention-bounded ([#241](https://github.com/vavallee/bindery/issues/241), v1.2.x). Download the filtered log as a text file from Settings → Logs ([#1903](https://github.com/vavallee/bindery/issues/1903), v1.31.0). Live warning at the Import Mode selector when Hardlink is picked on a setup that cannot hardlink ([#1720](https://github.com/vavallee/bindery/issues/1720), v1.31.0). -**UI** — Full i18n: catalogue extraction (v0.12.0), runtime language switcher persisted before first paint, locale-aware date/number formatting, and `Accept-Language` auto-detect with manual override. Editable quality profiles with a full create/rename/delete editor; note the format allow-list was UI-only until v1.28.2 actually enforced it ([#1693](https://github.com/vavallee/bindery/issues/1693)). +**UI** — Full i18n: catalogue extraction (v0.12.0), runtime language switcher persisted before first paint, locale-aware date/number formatting, and `Accept-Language` auto-detect with manual override. Editable quality profiles with a full create/rename/delete editor; note the format allow-list was UI-only until v1.28.2 actually enforced it ([#1693](https://github.com/vavallee/bindery/issues/1693)). Norwegian (Bokmål) and Swedish translations ([#2985](https://github.com/vavallee/bindery/issues/2985), v1.42.0), and book ratings shown in the Books, author and Add to Library views ([#3124](https://github.com/vavallee/bindery/issues/3124), v1.42.0). ## Won't do diff --git a/docs/User-Guide-Wiki.md b/docs/User-Guide-Wiki.md index 2bb60f8ed..78d15632a 100644 --- a/docs/User-Guide-Wiki.md +++ b/docs/User-Guide-Wiki.md @@ -141,7 +141,14 @@ default; tick it to bring every book of those authors into line in one action. Two related labels: -- **In Library** = status `imported` = Bindery can see the file on disk. +- **In Library** = Bindery can see at least one file for the book on disk. That is + status `imported`, and also a `wanted` book of format *Both* that already has + one of its two formats: it stays on the Wanted page while the other format is + missing, and it is listed under In Library as well (#3132). The Books page, + an author page's In library count and the have / total number on the Authors + list all use this one rule. With a format filter set, the book has to own + that format, so Audiobook plus In Library does not list a book that only has + an ebook. - **Exclude vs Delete**: a metadata refresh only adds books back for an author you monitor and have set to take new items, so for those authors deleting an unwanted catalogue book is temporary — the next refresh recreates it. @@ -1155,9 +1162,19 @@ name**, which is the display name rewritten as "Last, First". Bindery derives it when the author is created, and a metadata refresh replaces it with the provider's own sort name whenever the provider supplies one. -So the Authors page is sorted by last name out of the box. **A to Z** in the -Sort menu gives you Asimov, Atwood, Bardugo; the two **First name** entries are -the ones that file Isaac under I. +So the Authors page is sorted by last name out of the box. **Last name A to Z** +in the Sort menu gives you Asimov, Atwood, Bardugo; the two **First name** +entries are the ones that file Isaac under I (#2805). The merge authors dialog +lists authors in the same last name order, and the Books grid's sort menu offers +**Author A to Z** and **Z to A**, which only the table's column header reached +before. + +The Books column of the Authors table reads *have / total*, for example 5 / 12, +and each card in the grid says how many of the author's books are in your library, +by the In Library rule under rule 2 near the top of this page (#3092). An author with no books shows a dash. +On an author's own page the book list has a **Sort** control (title, published, +added, each both ways) and Monitored and Language filters beside Type, Status and +Published (#3087); books with no release date sort last. | Display name | Files under | Rule | |---|---|---| @@ -1228,7 +1245,10 @@ defined rather than translated per language. Knowing the edges saves time: -- **Track reading.** No read/unread, progress, or ratings. Bindery acquires +- **Track reading.** No read/unread, progress, or ratings of your own. Bindery + shows the community rating the metadata provider supplies (on the book page, + in the Books and author tables and grids, and in Add to library; the Books + table sorts by it, unrated books last), but does not record yours. Bindery acquires and organises; Audiobookshelf, Hardcover, or your reader do consumption. - **Identify arbitrary releases.** No release-first flow (see the top of this page). From 65b1d6e33362614404fed33a95a6ac49a98a2a59 Mon Sep 17 00:00:00 2001 From: vavallee Date: Sun, 11 Oct 2026 23:57:51 -0300 Subject: [PATCH 2/5] docs: document the audit and cover upload behaviour merged for v1.42.0 Download retention in the README environment table and the user guide (Queue hides imported rows after 7 days), the linked author folder note for file deletes, migration 107 and batched log writes under Upgrading, the cover upload status codes and response, and ROADMAP Delivered entries. Documents #3187, #3175, #3185, #3171. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee --- README.md | 1 + docs/API.md | 2 +- docs/DEPLOYMENT.md | 8 ++++++++ docs/ROADMAP.md | 2 +- docs/User-Guide-Wiki.md | 6 ++++-- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e091fe7a9..aa46f7f0d 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,7 @@ Bindery is configured through the web UI under **Settings** — indexers, downlo | `BINDERY_AUDIOBOOK_DOWNLOAD_DIR` | inherits download dir | Separate watch folder for audiobook downloads | | `BINDERY_URL_BASE` | _(empty)_ | Reverse-proxy subpath (e.g. `/bindery`) | | `BINDERY_OUTBOUND_PROXY` | _(empty)_ | Route outbound HTTP (indexers, metadata, covers, notifications, telemetry) through an `http`/`https`/`socks5` proxy. LAN/loopback destinations bypass it by default — see [DEPLOYMENT.md](docs/DEPLOYMENT.md#environment-variables) | +| `BINDERY_DOWNLOAD_RETENTION_DAYS` | `90` | Days to keep a finished (imported or failed) download row before a scheduled job deletes it; `0` keeps every row. See [DEPLOYMENT.md](docs/DEPLOYMENT.md#environment-variables) before upgrading a long running install | | `BINDERY_LOG_LEVEL` | `info` | Log verbosity: `debug`, `info`, `warn` or `error` | | `BINDERY_PUID` / `BINDERY_PGID` | _(unset)_ | Sanity-check assertions for the container UID/GID. They only assert; the distroless image does no runtime user switching, so set the UID with `--user` or Compose's `user:` as well | diff --git a/docs/API.md b/docs/API.md index ddbf04c6a..c9e87f549 100644 --- a/docs/API.md +++ b/docs/API.md @@ -346,7 +346,7 @@ PUT /api/v1/book/{id}/exclude exclude from future searches GET /api/v1/book/{id}/series-exclusions the series the book was taken out of: [{seriesForeignId, seriesId, seriesTitle, position}]; seriesId is 0 when nothing resolves the id any more POST /api/v1/book/{id}/rebind re-link to a different metadata record POST /api/v1/book/{id}/enrich-audiobook pull narrator/duration/cover from Audnex -POST /api/v1/book/{id}/cover set the cover from the request body (raw JPEG, PNG, WebP or GIF, max 10 MB); stored locally, and a refresh does not replace it +POST /api/v1/book/{id}/cover set the cover from the request body (raw JPEG, PNG, WebP or GIF, max 10 MB); answers 413 over the limit and 415 for anything that is not one of those four; stored locally, replaces the current cover, and a refresh does not replace it. Answers the full book, as `GET /book/{id}` does POST /api/v1/book/{id}/search manual indexer search GET /api/v1/book/{id}/file download the imported file (auth required; `?path=…` serves one specific tracked file, for a book holding several of a format; `?format=ebook|audiobook` picks the format on dual-format books; `?path=` wins when both are sent) GET /api/v1/book/{id}/calibre where the book stands in the Calibre delivery queue (see Calibre below) diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 18c4220d0..eae4eb29a 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -217,6 +217,8 @@ The frontend is embedded in the binary via `go:embed` — no separate static-fil Deleting a book or an author with its files removes only files inside a root folder, `BINDERY_LIBRARY_DIR` or `BINDERY_AUDIOBOOK_DIR`, after resolving symlinks on both sides. A root that is itself a symlink to another volume works, but a symlink inside a root that points outside it is not followed. A path outside every root is skipped with a warning in the log (`path containment: refusing to delete file`) and the database row is still removed. With no root at all every file delete is refused until one is set; a default install cannot reach that state, because `BINDERY_LIBRARY_DIR` defaults to `/books`. A Calibre library that lives outside your roots is not covered by deletes from Bindery, as before. +**Linked author folders.** If an author folder in your library is a symlink to another disk (`/books/Author -> /mnt/disk2/Author`), the file's real location is `/mnt/disk2/Author/...`, which is outside `/books`. Add the target (`/mnt/disk2` or the folder you link to) as an additional root folder (Settings, Root Folders), otherwise deleting a book from Bindery removes the row and leaves its files on disk, with the warning above in the log. + ## Caching and large downloads behind a reverse proxy The web app's build files under `/assets/` carry a content hash in their name, and Bindery serves them with `Cache-Control: public, max-age=31536000, immutable`. A proxy or CDN in front of Bindery may cache them for as long as it likes, because a new release changes the names. Nothing else is cached this way: `index.html` is `no-store`, and files such as the favicon and the manifest carry no cache headers, so clients fetch them again. If your proxy rewrites `Cache-Control` on `/assets/`, leave the long lifetime alone. @@ -689,6 +691,12 @@ the upgrade. It runs during startup, before Bindery begins serving, and adds no migration; on a development machine 100,000 books took about 1.4 seconds. Rows that now share a key are neither merged nor removed: they show up together in **Review duplicates** for you to decide. +### Startup work and background changes in v1.42.0 + +- **Migration 107** adds four indexes (History by event type, pending releases per book, the newest-first downloads list and the quality profile in-use check). They are built once at startup, about half a second on a database with 200,000 history rows. +- **Finished download rows are trimmed.** The first pass of the retention job runs about six hours after the first start of the new version and deletes every `imported` or `failed` row older than `BINDERY_DOWNLOAD_RETENTION_DAYS` (default 90) in batches. On a long running install that is most of the table. Set the variable to `0` before that first start to keep the rows. See the variable's row in the environment table. +- **Log lines are written in batches** of up to 100 records or 250 ms. After a crash or power loss the newest 250 ms of log rows can be missing from Settings, Logs; a normal shutdown writes everything. + ### Calibre library covers repair Versions before the #2564 fix stored each Calibre-imported book's cover as the library's absolute path in `editions.image_url`, which nothing could serve. On every start Bindery now runs a background pass over such rows: each readable `cover.jpg` is copied into `covers/` under `BINDERY_DATA_DIR` and the edition (and the book, when it has no provider cover) is repointed at the copy. No schema migration is involved. The Calibre library must be mounted at the path recorded in the rows for the pass to read the files; rows it cannot read are left untouched and retried on the next start, and a library import rewrites them as well. Progress is logged as `calibre cover repair finished` at `info`. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 94b4f31bc..7e676149f 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -50,7 +50,7 @@ The Calibre-watched drop folder ([#64](https://github.com/vavallee/bindery/issue **Unattended release discovery**: monitored authors are checked for new books on a schedule, off until you pick Daily, Weekly or Monthly in Settings, General, New release discovery, plus a `bookAnnounced` webhook listing what a refresh or a scheduled check added ([#2236](https://github.com/vavallee/bindery/issues/2236), v1.37.0). -**Storage and privacy** — Import modes move / copy / hardlink ([#54](https://github.com/vavallee/bindery/issues/54), v0.12.0). Configurable default root folder ([#332](https://github.com/vavallee/bindery/issues/332), v1.2.0). Server-side cover-image proxy and cache so the browser never contacts third-party image hosts ([#112](https://github.com/vavallee/bindery/issues/112), ~v0.16.0). Persistent structured log store in SQLite, queryable and retention-bounded ([#241](https://github.com/vavallee/bindery/issues/241), v1.2.x). Download the filtered log as a text file from Settings → Logs ([#1903](https://github.com/vavallee/bindery/issues/1903), v1.31.0). Live warning at the Import Mode selector when Hardlink is picked on a setup that cannot hardlink ([#1720](https://github.com/vavallee/bindery/issues/1720), v1.31.0). +**Storage and privacy** — Import modes move / copy / hardlink ([#54](https://github.com/vavallee/bindery/issues/54), v0.12.0). Configurable default root folder ([#332](https://github.com/vavallee/bindery/issues/332), v1.2.0). Server-side cover-image proxy and cache so the browser never contacts third-party image hosts ([#112](https://github.com/vavallee/bindery/issues/112), ~v0.16.0). Persistent structured log store in SQLite, queryable and retention-bounded ([#241](https://github.com/vavallee/bindery/issues/241), v1.2.x). Download the filtered log as a text file from Settings → Logs ([#1903](https://github.com/vavallee/bindery/issues/1903), v1.31.0). Live warning at the Import Mode selector when Hardlink is picked on a setup that cannot hardlink ([#1720](https://github.com/vavallee/bindery/issues/1720), v1.31.0). Set a book's cover from an uploaded image ([#3017](https://github.com/vavallee/bindery/issues/3017), v1.42.0). A security and performance audit ([#1640](https://github.com/vavallee/bindery/issues/1640), v1.42.0): file deletes confined to your root folders, one JSON error shape with no internal text, address checks on download client and proxy connections, trimmed finished download rows, an indexed and paged Wanted list, batched log writes, cached assets and uncapped file downloads; the findings are in `docs/audits/2026-10-bindery-security-errors-performance.md`. **UI** — Full i18n: catalogue extraction (v0.12.0), runtime language switcher persisted before first paint, locale-aware date/number formatting, and `Accept-Language` auto-detect with manual override. Editable quality profiles with a full create/rename/delete editor; note the format allow-list was UI-only until v1.28.2 actually enforced it ([#1693](https://github.com/vavallee/bindery/issues/1693)). Norwegian (Bokmål) and Swedish translations ([#2985](https://github.com/vavallee/bindery/issues/2985), v1.42.0), and book ratings shown in the Books, author and Add to Library views ([#3124](https://github.com/vavallee/bindery/issues/3124), v1.42.0). diff --git a/docs/User-Guide-Wiki.md b/docs/User-Guide-Wiki.md index 78d15632a..4ae1b7646 100644 --- a/docs/User-Guide-Wiki.md +++ b/docs/User-Guide-Wiki.md @@ -498,7 +498,7 @@ the recovery actions: **Retry import** (after fixing a path remap), **Retry download** on a row that failed before its files arrived (this re-sends the same release, it does not search for a different one), **Match to book** (attach a failed import to the right book and import it from disk), and -per-row error detail. Tick rows for **Retry selected**, or use **Retry all +per-row error detail. An `imported` row leaves the Queue after 7 days (it stays on History), failed and blocked rows stay until you deal with them, and Bindery deletes finished download rows older than `BINDERY_DOWNLOAD_RETENTION_DAYS` (90 by default, `0` keeps them). Once a row is trimmed, Bindery no longer remembers that exact release was grabbed, so if you later delete the file and the book is wanted again, the same release can be grabbed again. Tick rows for **Retry selected**, or use **Retry all failed** to cover every failed row at once. If a download client does not answer in time the page says so above the list, so a short Queue is never mistaken for lost downloads. History records every grab/import/failure and can @@ -1269,7 +1269,9 @@ Knowing the edges saves time: files that resolve to a place inside your library folders, so files behind a linked folder that points elsewhere stay on disk (the log says `path containment: refusing to delete file`) and the book is still removed - from Bindery. + from Bindery. If you link author folders to another disk, add the link + target as an extra root folder (Settings, Root Folders), or deleting a book + there leaves its files behind. Library Scan follows a library or audiobook folder that is itself a symlink and records the books under the path you configured, but it does not descend into a linked folder *inside* it, so books under a linked author From 64a4210a3ab81f7f472053c25de619502d583658 Mon Sep 17 00:00:00 2001 From: vavallee Date: Sun, 11 Oct 2026 23:58:55 -0300 Subject: [PATCH 3/5] chore(release): assemble the v1.42.0 changelog Assembles the v1.42.0 section of CHANGELOG.md from the changelog.d fragments and clears the directory (README.md stays). Upgrade notes up front for the audit API and behaviour changes (413, JSON 404 and 405, Prowlarr test boolean, grab error codes, delete containment and linked author folders, OIDC list, password limits, download retention, wanted envelope, batched logs, migrations 102 and 107), the stricter umlaut matching, the one time dedup key rewrite and the junk folder skips. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee --- CHANGELOG.md | 84 +++++++++++++++++++ changelog.d/1640-security-hardening.md | 10 --- changelog.d/2524-split-editions.md | 2 - changelog.d/2554-book-series.md | 10 --- changelog.d/2593-hardcover-daily-hold.md | 2 - changelog.d/2805-author-sort-names.md | 2 - changelog.d/2980-local-file-covers.md | 2 - changelog.d/2985-norwegian-swedish.md | 2 - changelog.d/3017-book-cover-upload.md | 2 - changelog.d/3018-cover-language.md | 2 - changelog.d/3058-author-sync-on-load.md | 2 - changelog.d/3081-compact-selects-touch.md | 2 - changelog.d/3087-author-books-sort-filter.md | 2 - changelog.d/3092-authors-list-have-count.md | 2 - .../3101-rejected-hardcover-token-hold.md | 2 - changelog.d/3122-filing-form-dedup-key.md | 2 - changelog.d/3123-umlaut-flex.md | 2 - changelog.d/3124-book-ratings.md | 2 - changelog.d/3126-add-to-library-filter.md | 5 -- changelog.d/3132-in-library-partial.md | 2 - changelog.d/3135-nas-junk-folders.md | 2 - ...5-scan-skip-hidden-and-download-folders.md | 2 - changelog.d/3137-nfd-release-titles.md | 2 - .../3139-multidisc-audiobook-torrents.md | 5 -- changelog.d/3144-metadata-bnf.md | 2 - .../3146-stored-covers-and-broken-images.md | 2 - changelog.d/3148-select-arrow-padding.md | 2 - changelog.d/3172-genre-idf.md | 2 - changelog.d/3177-http-static-downloads.md | 7 -- changelog.d/api-error-contract.md | 2 - changelog.d/api-error-leaks.md | 2 - changelog.d/go-1.26.9.md | 2 - changelog.d/perf-db-queries.md | 5 -- changelog.d/pollers-retention.md | 2 - 34 files changed, 84 insertions(+), 96 deletions(-) delete mode 100644 changelog.d/1640-security-hardening.md delete mode 100644 changelog.d/2524-split-editions.md delete mode 100644 changelog.d/2554-book-series.md delete mode 100644 changelog.d/2593-hardcover-daily-hold.md delete mode 100644 changelog.d/2805-author-sort-names.md delete mode 100644 changelog.d/2980-local-file-covers.md delete mode 100644 changelog.d/2985-norwegian-swedish.md delete mode 100644 changelog.d/3017-book-cover-upload.md delete mode 100644 changelog.d/3018-cover-language.md delete mode 100644 changelog.d/3058-author-sync-on-load.md delete mode 100644 changelog.d/3081-compact-selects-touch.md delete mode 100644 changelog.d/3087-author-books-sort-filter.md delete mode 100644 changelog.d/3092-authors-list-have-count.md delete mode 100644 changelog.d/3101-rejected-hardcover-token-hold.md delete mode 100644 changelog.d/3122-filing-form-dedup-key.md delete mode 100644 changelog.d/3123-umlaut-flex.md delete mode 100644 changelog.d/3124-book-ratings.md delete mode 100644 changelog.d/3126-add-to-library-filter.md delete mode 100644 changelog.d/3132-in-library-partial.md delete mode 100644 changelog.d/3135-nas-junk-folders.md delete mode 100644 changelog.d/3135-scan-skip-hidden-and-download-folders.md delete mode 100644 changelog.d/3137-nfd-release-titles.md delete mode 100644 changelog.d/3139-multidisc-audiobook-torrents.md delete mode 100644 changelog.d/3144-metadata-bnf.md delete mode 100644 changelog.d/3146-stored-covers-and-broken-images.md delete mode 100644 changelog.d/3148-select-arrow-padding.md delete mode 100644 changelog.d/3172-genre-idf.md delete mode 100644 changelog.d/3177-http-static-downloads.md delete mode 100644 changelog.d/api-error-contract.md delete mode 100644 changelog.d/api-error-leaks.md delete mode 100644 changelog.d/go-1.26.9.md delete mode 100644 changelog.d/perf-db-queries.md delete mode 100644 changelog.d/pollers-retention.md diff --git a/CHANGELOG.md b/CHANGELOG.md index ba87d9e6c..7dd4776ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,90 @@ All notable changes to Bindery are documented here. Format loosely follows [Keep a Changelog](https://keepachangelog.com) and versions follow [Semantic Versioning](https://semver.org). +## [v1.42.0] - 2026-10-12 + +A security and performance audit, a French national library, book ratings, and a long run of library scan, search and cover fixes + +This release has two halves. The first is an audit of the API, the database and the downloads path: error messages no longer carry database, filesystem or upstream text, file deletes stay inside your library folders, connections from download clients and the proxy are address checked, finished download rows are trimmed after 90 days, the Wanted list and the download pollers stop reading whole tables, large audiobook downloads no longer stop at two minutes, and the web app's files are cached for a year. Several API statuses changed along the way, and the upgrade notes list every one. The findings are written up in `docs/audits/2026-10-bindery-security-errors-performance.md`. The second half is features and fixes. The Bibliothèque nationale de France joins as an opt in metadata provider, book ratings show in the UI, you can set a book's cover from an uploaded image or from its own file, fix a single book's series from the book, and use Norwegian and Swedish translations. Add to Library can filter results to books or authors, and the Authors list shows how many of each author's books you have. Much of it came from francisrath and johnistheman, who reported and wrote it: the library scan now leaves torrent, NAS and snapshot folders alone, multi-disc audiobook torrents import again, releases a tracker stores with decomposed accents are found, and covers Bindery stores itself show everywhere. The Go toolchain moves to 1.26.9, which fixes eleven standard library vulnerabilities. Two database migrations (102 and 107) and one rewrite of the stored duplicate keys run on their own at startup. Please read the upgrade notes before updating a long running install. + +### Upgrade notes +**API and behaviour changes from the audit.** If you script against the API, run a reverse proxy in front of Bindery, link author folders to another disk, or have run Bindery for a long time, read these first. + +- **Deleting a book or author with its files refuses paths whose real location is outside every library root** (#1640, #3175). Bindery resolves symlinks on both the file and the roots before it deletes. If you link an author folder to another disk (`/books/Author -> /mnt/disk2/Author`), add the target (`/mnt/disk2`, or the folder you link to) as an extra root folder under Settings, Root Folders. Otherwise deleting that book drops its row from Bindery, logs `path containment: refusing to delete file` and leaves the files on disk. A library root that is itself a symlink keeps working, and with no root at all every file delete is now refused (a default install always has `/books`). +- **Finished download rows older than `BINDERY_DOWNLOAD_RETENTION_DAYS` (default 90) are deleted** (#3187). A scheduled job removes `imported` and `failed` rows, in batches, and its first pass runs about six hours after the first start of the new version. On an install that has run a long time that is most of the downloads table, in one go. **Set `BINDERY_DOWNLOAD_RETENTION_DAYS=0` before the first start of the new version if you want to keep them.** History, the blocklist and your library are never touched, and live or waiting rows (including `importFailed` and `importBlocked`) are kept. Once an imported row is gone Bindery no longer remembers that exact release was grabbed, so if the book becomes wanted again the same release can be grabbed again instead of being refused as already imported. +- **The Queue page hides `imported` rows older than 7 days** (#3187). They are on History. Failed and blocked rows stay until you deal with them. +- **`GET /api/v1/wanted/missing` returns the `{items, total, limit, offset}` envelope only when you pass `limit`** (#3187). Without `limit` it still answers the bare array, so existing scripts are unaffected. The Wanted page now pages and searches on the server. +- **An oversized JSON body answers `413` instead of `400`** (#3176). A body over the 1 MiB limit on the indexer, download client, notification, import list, profile and custom format create and update calls, the genre calls and the manual import calls answers `413` with `{"error": "request body too large"}`. An oversized migration upload also answers `413`. +- **Unknown API paths answer JSON** (#3176). A path that does not exist under `/api` or `/api/v1` answers `404` with `{"error": "not found"}`, and a wrong method answers `405` with `{"error": "method not allowed"}` and an `Allow` header, where they used to be the plain text `404 page not found` and an empty `405`. A crash inside a handler answers `{"error": "internal server error", "request_id": "..."}`, and the request id is in Settings, Logs. +- **`POST /api/v1/prowlarr/{id}/test` returns `ok` as a JSON boolean** (#3176). It used to be the string `"true"` or `"false"`. Scripts comparing against the string need updating. +- **Grab and retry errors carry a code and fixed texts** (#3174). A grab or retry whose download client refuses the release or cannot be reached answers `502` with `failed to send the release to the download client`. A missing download client is a `400` with `"code": "no_download_client"`, and the web app now keys on the code instead of the message text, so a script matching the old message text should match the code. A download client connection test that fails after reaching the client answers `502` instead of `400`. A rollback of an unknown Audiobookshelf run answers `404` and an unavailable one `503` instead of `400`, and changing the role of an unknown user answers `404`. Lookups that failed because the database was unavailable answer `500` instead of `404`. +- **The OIDC providers list is trimmed for anyone who is not an admin, and `/auth/oidc/redirect-base` is admin only** (#1640, #3175). `GET /api/v1/auth/oidc/providers` gives the login page what it needs but returns `last_error`, `last_attempt` and `allowed_groups` only to an admin. Behind a proxy, `X-Forwarded-Proto` must be `http` or `https` and `X-Forwarded-Host` a plain host with an optional port before either is used to build the OIDC redirect URL. +- **Changing your password is rate limited** (#3175). The sixth wrong current password in a row answers `429`, like the login form. A successful change clears the count. Stored password hashes that ask for more than 256 MiB, 10 passes or 8 threads of argon2 are refused instead of run. +- **Log lines are written in batches** (#3185). After a crash or power loss, up to the newest 250 ms of log rows can be missing from Settings, Logs. A normal shutdown writes everything. +- **Migration 107 adds four indexes at startup** (#3185). About half a second on a database with 200,000 history rows. +- **Large downloads and zips** (#3177). A book or audiobook download is no longer cut off at two minutes, only dropped if the client stops reading for five minutes. Audiobook zips store audio, ebook and image files uncompressed, so they build far faster. + +**Other behaviour changes.** + +- **Umlaut matching is stricter** (#3123, #3156). A release naming "Michal" no longer matches the author Michael, and Samuel no longer matches "Samul". Only a real ä, ö or ü is flexible now, so "Müller" still finds Müller, Mueller and Muller releases. The trade off: a name you typed in plain ASCII is taken as typed, so a book by "Mueller" no longer matches a release spelled "Muller", and an ASCII spelled German author no longer matches a release that dropped the vowel. If a release you expect stops matching, search for it by hand. +- **Stored duplicate keys are rewritten once on the first start** (#3122, #3158). A title filed with its article last (`Trace of Death, A`) now has the same key as `A Trace of Death`. Every book's key is recomputed during the first boot, which took about 1.4 seconds for 100,000 books on a development machine. Rows that now share a key are not merged or removed. They stay as they are and show up together in Review duplicates. +- **In Library now includes partly owned dual format books** (#3132, #3134). A book set to Both that has one format imported is listed under In Library as well as Wanted, and the author page filter and In library count follow the same rule. If you used In Library to mean "every format it wants is here", it now means "at least one file is here". +- **A rejected Hardcover token pauses Hardcover for up to an hour** (#3101, #3160). When Hardcover refuses the saved API token, Bindery stops asking with it. Saving a new token lifts the pause at once, Test connection still reaches Hardcover, and one probe request an hour lets a restored token recover by itself. Settings shows a notice while the pause is on. +- **The library scan skips known junk folders** (#3135, #3136, #3155). Torrent client and sync folders (`.torrents`, `.Trash-*`, `.incomplete`, `.AppleDouble`, `.stfolder`), NAS thumbnail, recycle and snapshot folders (`@eaDir`, `#recycle`, `.@__thumb`, `.zfs`, `.snapshots`, `lost+found`), your configured download folders when they sit inside a library folder, and any folder holding a `.binderyignore` file are no longer walked. Entries an earlier scan listed from those folders disappear on the next scan. Other folders that start with a dot are still scanned. +- **Audiobook torrents in their own folder are placed whole from every client** (#3139, #3140). From qBittorrent, Deluge and rTorrent a flat multi-file audiobook used to be placed file by file, taking only the audio. It is now placed as a folder, as from Transmission, so cover art, `.nfo` and other files in the torrent's folder come along, and in move mode the torrent's folder is removed afterwards. A qBittorrent torrent added without a root folder is still placed file by file. +- **BnF is opt in and fetches no covers** (#3144, #3150). An install that does not select it in Settings, Metadata Profiles never contacts the BnF. +- **Database migrations 102 and 107 run automatically at startup**: 102 adds the table that remembers which series a book was taken out of (#2554), and 107 adds the four indexes above (#3185). Both are additive and need nothing from you. + +### Security +- **Error responses no longer carry database, filesystem or upstream text** (#3174, #3176). Several API endpoints answered a failure with the raw error string, which could include a metadata provider's request URL with its API key and the resolver address, a SQL driver message, a filesystem path from a failed `stat`, or whatever page a download client or Prowlarr returned. They now answer with a fixed message and keep the detail in the Bindery log. This covers grab and retry of a queue item (and the per row results of a bulk retry or remove), the book rebind and audiobook enrich calls, the author relink calls, add book and adoption, adding a root folder (which still says whether the path is missing, unreadable or under a file), manual import paths and the Fix Match preview, the user role change, the migration uploads and the Audiobookshelf import and rollback calls. Download client and Prowlarr connection tests still say what went wrong, but only the first line, with credentials redacted, at most 200 characters, and an unreachable client is named with the host and port you configured. A paused metadata provider (daily quota or rejected token) still says so. The visible status changes are in the upgrade notes. +- **File deletes refuse paths outside every library root, including when no root is configured, and a symlink inside a root that points outside it is no longer deletable** (#1640, #3175). The delete check used to accept the path as written and let everything through when no root resolved at all. Both the path and the root are now resolved, a file that is already gone can still be cleaned up, and a path with a `..` in it or one that runs through a link that cannot be resolved is refused. The refusal is logged with the path, and the row is still removed. See the upgrade note about linked author folders. +- **The outbound proxy no longer skips the connect time address check** (#3175). With `BINDERY_OUTBOUND_PROXY` set, a destination on the bypass list (`BINDERY_OUTBOUND_PROXY_BYPASS_LOCAL` or `BINDERY_OUTBOUND_PROXY_NO_PROXY`) was dialled directly with no check on the address it resolved to. Cover images, webhooks, Prowlarr and OIDC discovery now check those direct connections too. Your own proxy is still reachable on a LAN or loopback address. +- **Download clients and Audiobookshelf can no longer redirect Bindery to a link local or cloud metadata address** (#3175). Connections to SABnzbd, NZBGet, qBittorrent, Deluge, Transmission, rTorrent and Audiobookshelf are checked when they connect and on every redirect. LAN, loopback and Docker hostnames work as before, and these clients still do not use `BINDERY_OUTBOUND_PROXY`. A client saved on a `169.254.x.x` address now fails with a clear "link-local address" error. +- **Changing your password is rate limited, and stored argon2 settings are capped** (#3175). Six wrong current passwords in a row answer `429`, so a stolen session cookie cannot be used to guess the password. A stored hash asking for more than 256 MiB, 10 passes or 8 threads is rejected before any work is done, and one with zero passes or threads can no longer crash the login. Hashes written by Bindery are unaffected. +- **The public OIDC provider list no longer shows discovery errors or group names, and `redirect-base` is admin only** (#1640, #3175). Forwarded headers are validated before they are used to build the OIDC redirect URL. +- **Calibre and Readarr database paths with `?`, `#` or `%` in them are opened literally** (#3175). The path was pasted into the database connection string, so a folder name such as `lib ?mode=rwc` could change how, and which, file was opened. +- **Built with Go 1.26.9** (#3131). The binaries and the container image move from Go 1.26.8 to 1.26.9, which fixes eleven standard library vulnerabilities that `govulncheck` reports as reachable from Bindery's code: eight in `net/http` (GO-2026-6603, 6605, 6609, 6610, 6611, 6612, 6613, 6617), one in `net/textproto` (GO-2026-6608), one in `crypto/tls` (GO-2026-6607) and one in `os` (GO-2026-6604). Thanks to francisrath for the bump. + +### Added +- **Bibliothèque nationale de France metadata provider** (#3144, #3150). The BnF can be chosen as the primary metadata provider in Settings, Metadata Profiles. Legal deposit puts nearly every book published in France in its catalogue under the French edition title, so a French library whose indexers name releases after the French edition ("Alice au pays des merveilles") gets that title instead of the original English one other providers list. Books carry their language, publisher, year, ISBNs and page count, and the original title of a translation. Translators and illustrators are never taken for the author. It reads only BnF's printed book records, under the Licence Ouverte 2.0, credited in Settings and, with the date each record was retrieved, on every BnF book and author page, and it fetches no covers. It is opt in: an install that does not select it never contacts the BnF, and when selected it sends at most one request a second. Thanks to mnivoliez for the request. +- **Set a book's own cover** (#3017, #3171). **Upload cover** (or **Change cover**) under the cover on a book's page picks an image to use instead. Scripts can do the same through `POST /api/v1/book/{id}/cover`, which takes a JPEG, PNG, WebP or GIF image (up to 10 MB) as the request body, answers `413` over the limit and `415` for anything else, and returns the book. The image is stored by Bindery itself, like covers taken from files, so it does not depend on a remote host, and it replaces the current cover. Refresh and enrichment only fill an empty cover, so an uploaded one stays. Thanks to francisrath for the request and the change. +- **Book ratings in the UI** (#3124, #3125). The community rating Bindery already stores for each book now shows on the book detail page, in the Books table and grid, in an author's book table and grid, and on book results in Add to Library. The Books table can be sorted by rating, with unrated books last. Hover a rating to see how many ratings it is based on. OpenLibrary search results now carry a rating too. Thanks to johnistheman for the request and the change. +- **Books / Authors filter in Add to Library** (#3126, #3127). The result list has an All / Books / Authors control. The Add Book button opens on a flat list of books in relevance order and the Add Author button on authors only, so a title search no longer sits under a stack of author rows. Switching views is instant, and each view shows how many results it holds. The work count beside an author now also comes from Hardcover, and is kept when another provider's record for the same author wins the merge. Thanks to johnistheman for the request and the change. +- **Books you have, per author, on the Authors list** (#3092, #3095). The Books column in the table view reads as have / total, for example 5 / 12, and each card in the grid view says how many of the author's books are in your library. A book counts as in your library once at least one of its files is on disk, the same rule as the Books page and the In library number on the author's page. An author with no books shows a dash. Thanks to johnistheman for the request and the change. +- **Sort and more filters on an author's page** (#3087, #3088). The book list on an author's page has a Sort control: title ascending or descending, published newest or oldest, and added newest or oldest. It works in grid view and on phones, where the only sort before was a column header that is not shown. The Title column header sorts as well as Published. Two filters join Type, Status and Published: Monitored, and Language, which appears only when the author's books are in more than one language and treats spellings such as `en` and `eng` as the same language. Books with no release date now sort last instead of first. Thanks to johnistheman for the request and the change. +- **Fix a book's series from the book** (#2554, #3090). Edit metadata has a Series field, limited to the author's series, with a position and New series… to create one, so a book filed under the wrong series or missing its number can be fixed without going through the series page. No series takes the book out of every series it is in. Admin only. A book taken out of a series is no longer put back by the next refresh: the book page lists the series it was taken out of, with Restore, and Unlock all fields hands the book's series back to refresh. In the Hardcover missing list, Add on a book you took out puts it back in that series, and Fill all still leaves removed books out. Thanks to magrhino for the request and francisrath for the change. +- **Covers from your own files** (#2980, #3094). A book that no metadata provider has a cover for now takes one from its file: the art embedded in an M4B, MP3, FLAC or EPUB, or a `cover.jpg` or `folder.jpg` in a multi file audiobook's folder. Books already in the library pick theirs up on the next library scan. A provider's cover is never replaced. Thanks to francisrath for the request and the change. +- **Norwegian (Bokmål) and Swedish translations** (#2985, #2994). The web UI is now available as Norsk bokmål and Svenska, picked automatically from your browser language or chosen under Settings, General. Browsers set to Norwegian Nynorsk or plain Norwegian get Bokmål, and Swedish works for both Sweden and Finland. Thanks to francisrath for offering to proofread the Norwegian. + +### Changed +- **A book no longer takes a translation's cover** (#3018, #3093). When no metadata provider had a cover, Bindery looked one up by the ISBN of every edition of the work, translations included, so a book often showed a translated edition's cover with another title, typically the German one. Only editions in the book's own language, or with no language recorded, are used for that lookup now. A book whose only available cover is a translation's keeps no cover, and you can set one yourself. Thanks to francisrath for the report and the fix. +- **Author sorting says what it does** (#2805, #3116). The Authors list's default sort is labelled Last name A to Z and Z to A, next to the existing First name options, because it always sorted by last name. The merge authors dialog lists authors in that same order instead of by first name, and the Books grid's sort menu offers Author A to Z and Z to A, which only the table's column header reached before. Thanks to francisrath for the change. +- **Finished download rows are trimmed, and the Wanted list and the download pollers no longer read everything on every request** (#3187). Every grab added a row to the downloads table and nothing removed one, so the Queue page (polled every 5 seconds), the Wanted list and each 15 second download poll got slower the longer an install ran. See the upgrade notes for the retention window and the first pass. `GET /api/v1/wanted/missing` pages when you pass `limit`, and its search now runs on the server. The SABnzbd and NZBGet pollers look their downloads up once per poll instead of once per queue item. +- **Faster book lookups on big libraries** (#3185). Every book query used to build a table of the first file of each format across all book files before it answered, so opening one book cost the same as a whole library scan. Files are now looked up for the books on the page only. On a test library of 20,000 books and 30,000 files, opening a book went from about 54 ms to 0.13 ms, the first page of the Books list from about 66 ms to under 1 ms, and the Wanted list from about 98 ms to 20 ms. A new index makes filtering History by event type instant (the worst case on 200,000 history rows went from about 220 ms to under 0.1 ms), and three smaller indexes cover pending releases per book, the newest first downloads list and the in use check when deleting a quality profile. +- **Log writes are batched, and metadata cache eviction no longer scans the whole cache** (#3185). The persistent log is written in one transaction per 100 records or 250 ms instead of one write per line, which takes about 8 times less of the database connection's time. A full in memory metadata cache now samples 32 entries to make room instead of scanning every entry under its lock. +- **The web app's files are cached for a year** (#3177). Build files under `/assets/` carry a content hash in their name, so the browser and any proxy in front of Bindery can keep them without asking again. The app shell, favicon and manifest are still checked every time, so an upgrade reaches you straight away. +- **Audiobook zips build much faster** (#3177). Audio, ebook and image files are stored in the zip as they are instead of being compressed a second time. A zip of an 800 MiB audiobook folder went from about 14 seconds of CPU to well under a second. Text, cue sheets and PDF are still compressed. +- **Metadata lookups keep more connections open** (#3177). Open Library, Hardcover, Google Books and the other metadata providers keep up to 16 idle connections per host instead of 2, so a burst of parallel lookups reuses them instead of reconnecting. +- **One JSON error shape across the API** (#3176). See the upgrade notes for the `413`, `404`, `405`, `500` and Prowlarr test changes. The browser CSRF rejection and the OPDS Basic auth `429` and `503` are JSON now, and the CSRF one is labelled `application/json` (it was labelled `text/plain`). The new Error format section in `docs/API.md` lists every status. +- **Hardening of the test suite** (#1844 and #3130, #1845 and #3117, #1846 and #3129, #1847 and #3128). The lifetime context, search timeout, scheduler and recommender wiring, and outbound proxy transport tests now exercise behaviour instead of comparing pointers, so they fail when the behaviour breaks. Thanks to francisrath. + +### Fixed +- **Large audiobook downloads no longer stop at two minutes** (#3177). A book or audiobook download was cut off when it had been running for two minutes, however steadily the client was reading, which hit big files on a slow link. Downloads now run as long as the client keeps reading (one that stops for five minutes is dropped), and resuming with a range request still works. The OPDS download and the Calibre bridge file transfer are covered too. +- **Recommendations favour the genres you read, not the ones you read least** (#3172, #3173). The genre profile behind Discover weighed a genre by how rare it was among your genres rather than among your books, so the genre most of your library is in came out negative: books in it were scored down, and it was left out of the genre-based searches. Weights are now never negative, and your main genres count for recommendations. A genre on nearly every book is still discounted. Thanks to francisrath for the report and the fix. +- **Hardcover pauses until its daily quota resets** (#2593, #2699). When Hardcover reports no requests left in its daily bucket, Bindery stops sending requests with that token until the reset, keeps the pause across restarts, and shows when the token can be used again in Settings, API Keys. With Hardcover as the primary provider, bulk imports and background catalogue work stop on the hold instead of repeatedly asking an exhausted key. With Hardcover only an enricher, held calls fail at once and ISBN lookups fall through to the other providers. Short per minute throttling is handled as before. Thanks to magrhino for the report and the change. +- **Split edition catalogue rows no longer count as missing books you already own** (#2524, #2915). Some Hardcover catalogues list a novel's own split into parts editions beside the whole novel (The Way of Kings at position 1, with Part 1 and Part 2 at 1.1 and 1.2). Already owning the whole novel left those parts listed as missing and offered an add button that queued a duplicate download. They now show in their own collapsed section, linked to the book that covers them, and Fill gaps no longer creates them. Thanks to gchahcg for the fix and magrhino for the Stormlight and Wheel of Time catalogue data that shaped the rule. +- **Partly owned dual format books stay in In Library** (#3132, #3134). A book set to Both that already had one format imported showed under Wanted but vanished from Books, In Library. In Library now lists any book with at least one file on disk, and the book still appears under Wanted while a format is missing. With a format chosen, the book has to own that format. Thanks to SturmB for the report. +- **A rejected Hardcover token no longer costs a request per row** (#3101, #3160). A bulk import, discovery sweep, catalogue sync, list sync or scheduled refresh stops at the first rejection and says "Hardcover rejected the API token", instead of sending one doomed request for every row. Server errors and queries Hardcover refuses for other reasons do not pause anything. See the upgrade note above. +- **A title filed with its article last is the same book as the natural spelling** (#3122, #3158). A library row named `Trace of Death, A` (the way Calibre files sort titles) and a catalogue entry named `A Trace of Death` produced two different duplicate keys, so a catalogue sync created a second row for a book you already had. `The Hobbit` and `Hobbit, The`, `Der Prozess` and `Prozess, Der`, and the same shape for the other articles Review duplicates already knows now key as one title, and the sync and the importers match the row you have. Only a lone article after the last comma counts, so `Love, Actually` and `Me, Myself and I` are left alone. See the upgrade note above. +- **A release naming "Michal" no longer matches the author Michael** (#3123, #3156). Release matching let every "ae", "oe" and "ue" in a name be optional, so Michael matched Michal, Samuel matched Samul, and Joel, Noel, Raphael and Manuel had the same problem. Only a real ä, ö or ü is flexible now. See the upgrade note above for what that costs ASCII spelled names. +- **Releases a tracker stores with decomposed accents are found** (#3137, #3138). Some trackers store a release title decomposed (an `a` followed by a combining ring instead of `å`) and match search terms byte for byte, so every search for such a book came back empty, while searching the author alone found it. When a search finds nothing and the title or author has letters that decompose (å, é, ñ, ö and the like, but not ø or æ), Bindery now runs it once more with the decomposed spelling. The relevance check that decides whether an indexer's answer is about the book also reads a decomposed release title as the composed one. Thanks to francisrath for the report and the fix. +- **Multi-disc audiobook torrents import again** (#3139, #3140). A qBittorrent, Deluge or rTorrent download such as `Fjellvinden/Fjellvinden CD1/01 Spor 01.mp3` and `Fjellvinden/Fjellvinden CD2/01 Spor 01.mp3` was blocked with "two files in this download would be placed at the same path": the torrent's own folder was taken for the client's shared download folder, so each file was placed by its name alone. The folder is now placed whole with its disc folders, as it already was from Transmission. With multi-disc flattening on, or a per-track file template, the tracks are numbered across discs in disc order. Norwegian and Danish `Del 1` folders, and folders such as `Fjellvinden_Cd 01`, count as discs too. Blocked downloads import with Retry import. Thanks to francisrath for the report and the fix. +- **Library Scan leaves torrent folders and other non-library folders alone** (#3135, #3136, #3155). A torrent client saving into the library (for example `/data/audiobooks/.torrents`, so imports can hardlink and keep seeding) made the scan find every torrent copy again and list it as an unmatched book, one click from importing it or moving it out of the client's folder. The scan, the "already in the library" check and folder import now skip known junk and sync folders, NAS thumbnail, recycle and snapshot folders, the configured download folders when they sit inside a library folder, and any folder holding a `.binderyignore` file. Other folders that start with a dot are still scanned, because a book folder can be named that way (`...And Then There Were None`, `.hack Sign`). Put a `.binderyignore` file in one to skip it. Thanks to francisrath for the report and both fixes. +- **Covers Bindery stores itself show on the Wanted and Series pages, and a cover that fails to load shows "No cover"** (#3146, #3147). The Wanted list and the series endpoints returned stored covers (such as a Calibre library's cover) without routing them through the image proxy, the only way they load, so those rows showed a broken image. The endpoints that answer a change did the same, so saving a book, excluding it, deleting its file, enriching it or adding an author swapped its cover for "No cover" until the page was reloaded. They all hand back the proxied cover now. Every cover in the web app also falls back to the same placeholder as a book with no cover when its image fails to load, instead of the browser's broken-image icon. Thanks to francisrath for the report and the fix. +- **A newly added author's page shows that the catalogue is still being fetched** (#3058, #3145). Adding an author starts a catalogue sync that can spend minutes fetching a large catalogue, and meanwhile the page said "No books tracked for this author yet" until it was reloaded by hand. It now says the catalogue is being fetched, watches the sync, and shows the books when it ends. The same applies when a Refresh outlasts its one minute wait: the button is freed as before, and the page keeps watching the sync. It pauses in a hidden tab, retries a reload that failed, and stops with the error showing if the author is gone or the server keeps failing. Thanks to francisrath for the report and the fix. +- **Dropdowns on phones** (#3081, #3082). Dropdowns no longer render oversized on touch screens: they keep the size they were designed at, such as the format picker on Wanted and the Type, Status and Published filters on an author page, and the value still stays clear of the arrow. Text fields are unchanged, so tapping into one still does not zoom the page. The Delete file button on a book page is also a size smaller, to match the row it sits in. Thanks to johnistheman for the report and the fix. +- **Dropdown arrows no longer cover the selected value** (#3148, #3149). Every dropdown set a compact right padding that left no room for its arrow, so a dropdown as wide as its longest option (Media type showing "Audiobook", Per page showing "50") drew the arrow over the end of it. Dropdowns now keep exactly the room the arrow takes, on desktop as on touch screens, and the Wanted page's Format column is wide enough for the format names in every language. The pager's jump dropdown, meant to read as plain text, no longer draws an arrow at all. Thanks to francisrath for the report and the fix. + ## [v1.41.0] - 2026-10-08 Bindery on a phone, kinder metadata provider limits, and a long list of import and library fixes diff --git a/changelog.d/1640-security-hardening.md b/changelog.d/1640-security-hardening.md deleted file mode 100644 index 21e10dac2..000000000 --- a/changelog.d/1640-security-hardening.md +++ /dev/null @@ -1,10 +0,0 @@ -### Security -- **File deletes refuse paths outside every library root, including when no root is configured.** Deleting a book or an author with its files now checks that each file sits inside a root folder (or `BINDERY_LIBRARY_DIR` / `BINDERY_AUDIOBOOK_DIR`). Before, the check let everything through when no root resolved at all. A normal install always has `/books` or your own library folder, so nothing changes there. The refusal is logged with the path, and the row is still removed. -- **A symlink inside a library root that points outside it is no longer deletable.** The delete check used to accept the path as written even when it resolved somewhere else. Both the path and the root are now resolved, so a library root that is itself a symlink to another volume keeps working, and a file that is already gone can still be cleaned up. A path with a `..` in it, or one that runs through a link that cannot be resolved, is refused too. -- **The outbound proxy no longer skips the connect time address check.** With `BINDERY_OUTBOUND_PROXY` set, a destination on the bypass list (`BINDERY_OUTBOUND_PROXY_BYPASS_LOCAL` or `BINDERY_OUTBOUND_PROXY_NO_PROXY`) was dialled directly with no check on the address it resolved to. Cover images, webhooks, Prowlarr and OIDC discovery now check those direct connections too. Your own proxy is still reachable on a LAN or loopback address. -- **Download clients and Audiobookshelf can no longer redirect Bindery to a link local or cloud metadata address.** Connections to SABnzbd, NZBGet, qBittorrent, Deluge, Transmission, rTorrent and Audiobookshelf are checked when they connect and on every redirect. LAN, loopback and Docker hostnames work as before, and these clients still do not use `BINDERY_OUTBOUND_PROXY`. A client saved on a `169.254.x.x` address now fails with a clear "link-local address" error. -- **Changing your password is rate limited.** Six wrong current passwords in a row now return 429, the same as the login form, so a stolen session cookie cannot be used to guess the password. A successful change clears the count. -- **The public OIDC provider list no longer shows discovery errors or group names.** `GET /api/v1/auth/oidc/providers` still gives the login page what it needs, but `last_error`, `last_attempt` and `allowed_groups` are only returned to an admin. -- **`GET /api/v1/auth/oidc/redirect-base` is admin only, and forwarded headers are validated** (#1640). The endpoint now needs an admin. `X-Forwarded-Proto` must be `http` or `https`, and `X-Forwarded-Host` must be a plain host with an optional port, before either is used to build the OIDC redirect URL. Anything else is ignored and the request's own Host is used. -- **Stored password hashes with extreme argon2 settings are refused.** A hash asking for more than 256 MiB, 10 passes or 8 threads is rejected before any work is done, and one with zero passes or threads can no longer crash the login. Hashes written by Bindery are unaffected. -- **Calibre and Readarr database paths with `?`, `#` or `%` in them are opened literally.** The path was pasted into the database connection string, so a folder name such as `lib ?mode=rwc` could change how, and which, file was opened. diff --git a/changelog.d/2524-split-editions.md b/changelog.d/2524-split-editions.md deleted file mode 100644 index 4190c218d..000000000 --- a/changelog.d/2524-split-editions.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- **Split-edition catalogue rows no longer count as missing books you already own** (#2524): some Hardcover catalogs list a novel's own split-into-parts editions beside the whole novel (The Way of Kings at position 1, with "Part 1" and "Part 2" at 1.1 and 1.2). Already owning the whole novel left those parts listed as missing and offered an add button that queued a duplicate download. They now show in their own collapsed section, linked to the book that covers them, and Fill gaps no longer creates them. Thanks magrhino for the Stormlight and Wheel of Time catalogue data that shaped the rule. diff --git a/changelog.d/2554-book-series.md b/changelog.d/2554-book-series.md deleted file mode 100644 index 21c8ac743..000000000 --- a/changelog.d/2554-book-series.md +++ /dev/null @@ -1,10 +0,0 @@ -### Added -- **Fix a book's series from the book** (#2554). Edit metadata has a Series field, limited to the author's series, with a position and New series… to create one, so a book filed under the wrong series or missing its number can be fixed without going through the series page. No series takes the book out of every series it is in. Admin only. -- **Removed stays removed** (#2554). A book taken out of a series is no longer put back by the next refresh. The book page lists the series it was taken out of, with Restore, and Unlock all fields hands the book's series back to refresh. - -### Fixed -- **One primary series per book** (#2525). Adding a book to a series as primary now makes it the book's only primary series. -- **Position is optional** when adding a book to a series, as it already was on the server. -- **Series controls for other users.** Users who aren't admins no longer see series buttons that the server refuses (add, rename, merge, delete, fill, Hardcover link, a book's series in Edit metadata). They still see every series and what is missing. -- **Series grouping after a refresh.** The author page regroups its books by series after a refresh, without a reload. -- **Add on a removed book** (#2554). Clicking Add on a book you took out of a series, in the Hardcover missing list, now puts it back in that series. Before, it said it queued the book and left it missing. Fill all still leaves removed books out. diff --git a/changelog.d/2593-hardcover-daily-hold.md b/changelog.d/2593-hardcover-daily-hold.md deleted file mode 100644 index 4b5d2ca89..000000000 --- a/changelog.d/2593-hardcover-daily-hold.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- Stop Hardcover requests and bulk metadata work until the provider-reported daily quota reset, preserve the pause across restarts, and show the reset time in Settings (#2593). diff --git a/changelog.d/2805-author-sort-names.md b/changelog.d/2805-author-sort-names.md deleted file mode 100644 index ae843b2c4..000000000 --- a/changelog.d/2805-author-sort-names.md +++ /dev/null @@ -1,2 +0,0 @@ -### Changed -- **Author sorting says what it does** (#2805). The Authors list's default sort is labelled Last name A to Z and Z to A, next to the existing First name options; it always sorted by last name. The merge authors dialog lists authors in that same order instead of by first name, and the Books grid's sort menu offers Author A to Z and Z to A, which only the table's column header reached before. diff --git a/changelog.d/2980-local-file-covers.md b/changelog.d/2980-local-file-covers.md deleted file mode 100644 index 07bb29fda..000000000 --- a/changelog.d/2980-local-file-covers.md +++ /dev/null @@ -1,2 +0,0 @@ -### Added -- **Covers from your own files** (#2980). A book that no metadata provider has a cover for now takes one from its file: the art embedded in an M4B, MP3, FLAC or EPUB, or a `cover.jpg` or `folder.jpg` in a multi file audiobook's folder. Books already in the library pick theirs up on the next library scan. A provider's cover is never replaced. diff --git a/changelog.d/2985-norwegian-swedish.md b/changelog.d/2985-norwegian-swedish.md deleted file mode 100644 index fa4c6223a..000000000 --- a/changelog.d/2985-norwegian-swedish.md +++ /dev/null @@ -1,2 +0,0 @@ -### Added -- **Norwegian (Bokmål) and Swedish translations** (#2985). The web UI is now available as Norsk bokmål and Svenska, picked automatically from your browser language or chosen under Settings, General. Browsers set to Norwegian Nynorsk or plain Norwegian get Bokmål, and Swedish works for both Sweden and Finland. Thanks to francisrath for offering to proofread the Norwegian. diff --git a/changelog.d/3017-book-cover-upload.md b/changelog.d/3017-book-cover-upload.md deleted file mode 100644 index dd87d1c17..000000000 --- a/changelog.d/3017-book-cover-upload.md +++ /dev/null @@ -1,2 +0,0 @@ -### Added -- **Set a book's own cover** (#3017). **Upload cover** (or **Change cover**) under the cover on a book's page picks an image to use instead. Scripts can do the same through `POST /api/v1/book/{id}/cover`, which takes a JPEG, PNG, WebP or GIF image (up to 10 MB) as the request body and makes it the book's cover. The image is stored by Bindery itself, like covers taken from files, so it does not depend on a remote host. Refresh and enrichment only fill an empty cover, so an uploaded one stays. diff --git a/changelog.d/3018-cover-language.md b/changelog.d/3018-cover-language.md deleted file mode 100644 index b0d1862a6..000000000 --- a/changelog.d/3018-cover-language.md +++ /dev/null @@ -1,2 +0,0 @@ -### Changed -- **A book no longer takes a translation's cover** (#3018). When no metadata provider had a cover, Bindery looked one up by the ISBN of every edition of the work, translations included, so a book often showed a translated edition's cover with another title, typically the German one. Only editions in the book's own language, or with no language recorded, are used for that lookup now. A book whose only available cover is a translation's keeps no cover, and you can set one yourself. diff --git a/changelog.d/3058-author-sync-on-load.md b/changelog.d/3058-author-sync-on-load.md deleted file mode 100644 index 34116959c..000000000 --- a/changelog.d/3058-author-sync-on-load.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- **A newly added author's page shows that the catalogue is still being fetched** (#3058). Adding an author starts a catalogue sync that can spend minutes fetching a large catalogue, and meanwhile the page said "No books tracked for this author yet" until it was reloaded by hand. It now says the catalogue is being fetched, watches the sync, and shows the books when it ends. The same applies when a Refresh outlasts its one-minute wait: the button is freed as before, and the page keeps watching the sync. It pauses in a hidden tab, retries a reload that failed, and stops with the error showing if the author is gone or the server keeps failing. diff --git a/changelog.d/3081-compact-selects-touch.md b/changelog.d/3081-compact-selects-touch.md deleted file mode 100644 index e02f7c8b4..000000000 --- a/changelog.d/3081-compact-selects-touch.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- **Dropdowns on phones** (#3081). Dropdowns no longer render oversized on touch screens: they keep the size they were designed at, such as the format picker on Wanted and the Type, Status and Published filters on an author page, and the value still stays clear of the arrow. Text fields are unchanged, so tapping into one still does not zoom the page. The Delete file button on a book page is also a size smaller, to match the row it sits in. diff --git a/changelog.d/3087-author-books-sort-filter.md b/changelog.d/3087-author-books-sort-filter.md deleted file mode 100644 index 85b99b6db..000000000 --- a/changelog.d/3087-author-books-sort-filter.md +++ /dev/null @@ -1,2 +0,0 @@ -### Added -- **Sort and more filters on an author's page** (#3087). The book list on an author's page has a Sort control: title ascending or descending, published newest or oldest, and added newest or oldest. It works in grid view and on phones, where the only sort before was a column header that is not shown. The Title column header sorts as well as Published. Two filters join Type, Status and Published: Monitored, and Language, which appears only when the author's books are in more than one language and treats spellings such as `en` and `eng` as the same language. Books with no release date now sort last instead of first. diff --git a/changelog.d/3092-authors-list-have-count.md b/changelog.d/3092-authors-list-have-count.md deleted file mode 100644 index 69daa8f0a..000000000 --- a/changelog.d/3092-authors-list-have-count.md +++ /dev/null @@ -1,2 +0,0 @@ -### Added -- **Books you have, per author, on the Authors list** (#3092). The Books column in the table view now reads as have / total, for example 5 / 12, and each card in the grid view says how many of the author's books are in your library. A book counts as in your library once at least one of its files is on disk, the same rule as the Books page and the In library number on the author's page. An author with no books shows a dash instead of 0 / 0. diff --git a/changelog.d/3101-rejected-hardcover-token-hold.md b/changelog.d/3101-rejected-hardcover-token-hold.md deleted file mode 100644 index 1f787d16e..000000000 --- a/changelog.d/3101-rejected-hardcover-token-hold.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- **A rejected Hardcover token no longer costs a request per row** (#3101). When Hardcover refuses the saved API token (a 401, or a 403 that is an authentication failure), Bindery now stops asking with that token. A bulk import, discovery sweep, catalogue sync, list sync or scheduled refresh stops at the first rejection and says "Hardcover rejected the API token", instead of sending one doomed request for every row. Settings shows a notice, saving a new token lifts the pause immediately, **Test connection** still reaches Hardcover so you can retest after fixing the token, and one probe request an hour lets a restored token recover by itself. Server errors and queries Hardcover refuses for other reasons do not pause anything. diff --git a/changelog.d/3122-filing-form-dedup-key.md b/changelog.d/3122-filing-form-dedup-key.md deleted file mode 100644 index fe2bbffd7..000000000 --- a/changelog.d/3122-filing-form-dedup-key.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- **A title filed with its article last is the same book as the natural spelling** (#3122). A library row named `Trace of Death, A` (the way Calibre files sort titles) and a catalogue entry named `A Trace of Death` produced two different duplicate keys, so a catalogue sync created a second row for a book you already had. `The Hobbit` and `Hobbit, The`, `Der Prozess` and `Prozess, Der`, and the same shape for the other articles Review duplicates already knows now key as one title, and the sync and the importers match the row you have. Only a lone article after the last comma counts, so `Love, Actually` and `Me, Myself and I` are left alone. Existing rows are recomputed on the first start after the upgrade. Rows that would now share a key are not merged or removed: they stay as they are, and they show up together in Review duplicates for you to decide. diff --git a/changelog.d/3123-umlaut-flex.md b/changelog.d/3123-umlaut-flex.md deleted file mode 100644 index 175329f2e..000000000 --- a/changelog.d/3123-umlaut-flex.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- **A release naming "Michal" no longer matches the author Michael** (#3123): release matching let every "ae", "oe" and "ue" in a name be optional, so Michael matched Michal, Samuel matched Samul, and Joel, Noel, Raphael and Manuel had the same problem. Only a real ä, ö or ü is flexible now, so "Müller" still finds Müller, Mueller and Muller releases, and "Schönberg" and "Björn" keep their compact spellings. The catch is that a name typed in plain ASCII is taken as typed: a book by "Mueller" no longer matches a release spelled "Muller". diff --git a/changelog.d/3124-book-ratings.md b/changelog.d/3124-book-ratings.md deleted file mode 100644 index 839b6e2f3..000000000 --- a/changelog.d/3124-book-ratings.md +++ /dev/null @@ -1,2 +0,0 @@ -### Added -- **Book ratings in the UI** (#3124). The community rating Bindery already stores for each book now shows on the book detail page, in the Books table and grid, in an author's book table and grid, and on book results in Add to Library. The Books table can be sorted by rating, with unrated books last. Hover a rating in a table or on a card to see how many ratings it is based on. OpenLibrary search results now carry a rating too. diff --git a/changelog.d/3126-add-to-library-filter.md b/changelog.d/3126-add-to-library-filter.md deleted file mode 100644 index b1e7850ea..000000000 --- a/changelog.d/3126-add-to-library-filter.md +++ /dev/null @@ -1,5 +0,0 @@ -### Added -- **Books / Authors filter in Add to Library** (#3126). The result list has an All / Books / Authors control. The Add Book button opens on a flat list of books in relevance order and the Add Author button on authors only, so a title search no longer sits under a stack of author rows. Switching views is instant, and each view shows how many results it holds. - -### Changed -- **Author results show a work count more often** (#3126). The count beside an author in Add to Library now also comes from Hardcover, and is kept when another provider's record for the same author wins the merge. Previously it only appeared when OpenLibrary or NB supplied the row. diff --git a/changelog.d/3132-in-library-partial.md b/changelog.d/3132-in-library-partial.md deleted file mode 100644 index c21dd3980..000000000 --- a/changelog.d/3132-in-library-partial.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- **Partly owned dual format books stay in In Library** (#3132). A book set to Both that already had one format imported showed under Wanted but vanished from Books, In Library. In Library now lists any book with at least one file on disk, and the book still appears under Wanted while a format is missing. The author page filter and In library count follow the same rule. Thanks to SturmB for the report. diff --git a/changelog.d/3135-nas-junk-folders.md b/changelog.d/3135-nas-junk-folders.md deleted file mode 100644 index eb9e6fa52..000000000 --- a/changelog.d/3135-nas-junk-folders.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- **Library Scan also skips NAS and snapshot folders** (#3135). Synology's `@eaDir` and `#recycle`, QNAP's `.@__thumb`, ZFS and btrfs snapshot folders (`.zfs`, `.snapshots`) and `lost+found` are left out of the scan, the "already in the library" check and folder import, like the other non-library folders. A snapshot folder inside a library could otherwise make the scan find every book again. diff --git a/changelog.d/3135-scan-skip-hidden-and-download-folders.md b/changelog.d/3135-scan-skip-hidden-and-download-folders.md deleted file mode 100644 index 8c3476904..000000000 --- a/changelog.d/3135-scan-skip-hidden-and-download-folders.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- **Library Scan leaves torrent folders and other non-library folders alone** (#3135). A torrent client saving into the library (for example `/data/audiobooks/.torrents`, so imports can hardlink and keep seeding) made the scan find every torrent copy again and list it as an unmatched book, one click from importing it or moving it out of the client's folder. The scan, the "already in the library" check and folder import now skip known junk and sync folders (`.torrents`, `.Trash-*`, `.incomplete`, `.AppleDouble`, `.stfolder` and similar), the configured download folders when they sit inside a library folder, and any folder holding a `.binderyignore` file. Other folders that start with a dot are still scanned, because a book folder can be named that way (`...And Then There Were None`, `.hack Sign`); put a `.binderyignore` file in one to skip it. Entries an earlier scan listed from those folders disappear on the next scan. diff --git a/changelog.d/3137-nfd-release-titles.md b/changelog.d/3137-nfd-release-titles.md deleted file mode 100644 index 2c7a22f02..000000000 --- a/changelog.d/3137-nfd-release-titles.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- **Releases a tracker stores with decomposed accents are found** (#3137). Some trackers store a release title decomposed (an `a` followed by a combining ring instead of `å`) and match search terms byte for byte, so every search for such a book came back empty, while searching the author alone found it. When a search finds nothing and the title or author has letters that decompose (å, é, ñ, ö and the like, but not ø or æ), Bindery now runs it once more with the decomposed spelling. The relevance check that decides whether an indexer's answer is about the book also reads a decomposed release title as the composed one. diff --git a/changelog.d/3139-multidisc-audiobook-torrents.md b/changelog.d/3139-multidisc-audiobook-torrents.md deleted file mode 100644 index babeb776c..000000000 --- a/changelog.d/3139-multidisc-audiobook-torrents.md +++ /dev/null @@ -1,5 +0,0 @@ -### Fixed -- **Multi-disc audiobook torrents import again** (#3139). A qBittorrent, Deluge or rTorrent download such as `Fjellvinden/Fjellvinden CD1/01 Spor 01.mp3` and `…/Fjellvinden CD2/01 Spor 01.mp3` was blocked with "two files in this download would be placed at the same path": the torrent's own folder was taken for the client's shared download folder, so each file was placed by its name alone. The folder is now placed whole with its disc folders, as it already was from Transmission; with multi-disc flattening on, or a per-track file template, the tracks are numbered across discs in disc order. Norwegian and Danish `Del 1` folders, and folders such as `Fjellvinden_Cd 01`, count as discs too. Blocked downloads import with **Retry import**. - -### Changed -- **Audiobook torrents in their own folder are placed whole from every client** (#3139). A flat multi-file audiobook from qBittorrent, Deluge or rTorrent used to be placed file by file, taking only the audio. It is now placed as a folder, as from Transmission: cover art, `.nfo` and other files in the torrent's folder come along, and in move mode the torrent's folder is removed afterwards. A qBittorrent torrent added without a root folder is still placed file by file. diff --git a/changelog.d/3144-metadata-bnf.md b/changelog.d/3144-metadata-bnf.md deleted file mode 100644 index d0726ca83..000000000 --- a/changelog.d/3144-metadata-bnf.md +++ /dev/null @@ -1,2 +0,0 @@ -### Added -- **Bibliothèque nationale de France metadata provider** (#3144). The BnF can be chosen as the primary metadata provider in Settings, Metadata Profiles. Legal deposit puts nearly every book published in France in its catalogue under the French edition title, so a French library whose indexers name releases after the French edition ("Alice au pays des merveilles") gets that title instead of the original English one other providers list. Books carry their language, publisher, year, ISBNs and page count, and the original title of a translation; translators and illustrators are never taken for the author. It reads only BnF's printed book records, under the Licence Ouverte 2.0, credited in Settings and, with the date each record was retrieved, on every BnF book and author page, and fetches no covers. It is opt in: an install that does not select it never contacts the BnF, and when selected it sends at most one request a second. Thanks to mnivoliez for the request. diff --git a/changelog.d/3146-stored-covers-and-broken-images.md b/changelog.d/3146-stored-covers-and-broken-images.md deleted file mode 100644 index 90bcc564a..000000000 --- a/changelog.d/3146-stored-covers-and-broken-images.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- **Covers Bindery stores itself show on the Wanted and Series pages, and a cover that fails to load shows "No cover"** (#3146). The Wanted list and the series endpoints returned stored covers (such as a Calibre library's cover) without routing them through the image proxy, the only way they load, so those rows showed a broken image. The endpoints that answer a change did the same, so saving a book, excluding it, deleting its file, enriching it or adding an author swapped its cover for "No cover" until the page was reloaded. They all hand back the proxied cover now. Every cover in the web app now also falls back to the same placeholder as a book with no cover when its image fails to load, whatever the reason, instead of the browser's broken-image icon. diff --git a/changelog.d/3148-select-arrow-padding.md b/changelog.d/3148-select-arrow-padding.md deleted file mode 100644 index 3359ec9d5..000000000 --- a/changelog.d/3148-select-arrow-padding.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- **Dropdown arrows no longer cover the selected value** (#3148). Every dropdown set a compact right padding that left no room for its arrow, so a dropdown as wide as its longest option (Media type showing "Audiobook", Per page showing "50") drew the arrow over the end of it. Dropdowns now keep exactly the room the arrow takes, on desktop as on touch screens, and the Wanted page's Format column is wide enough for the format names in every language. The pager's "…" jump dropdown, meant to read as plain text, no longer draws an arrow at all. diff --git a/changelog.d/3172-genre-idf.md b/changelog.d/3172-genre-idf.md deleted file mode 100644 index 7f868dd9c..000000000 --- a/changelog.d/3172-genre-idf.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- **Recommendations favour the genres you read, not the ones you read least** (#3172). The genre profile behind Discover weighed a genre by how rare it was among your genres rather than among your books, so the genre most of your library is in came out negative: books in it were scored down, and it was left out of the genre-based searches. Weights are now never negative, and your main genres count for recommendations. A genre on nearly every book is still discounted. diff --git a/changelog.d/3177-http-static-downloads.md b/changelog.d/3177-http-static-downloads.md deleted file mode 100644 index 7f7f5b807..000000000 --- a/changelog.d/3177-http-static-downloads.md +++ /dev/null @@ -1,7 +0,0 @@ -### Fixed -- **Large audiobook downloads no longer stop at two minutes** (#3177). A book or audiobook download was cut off when it had been running for two minutes, however steadily the client was reading, which hit big files on a slow link. Downloads now run as long as the client keeps reading (one that stops for five minutes is dropped), and resuming with a range request still works. The OPDS download and the Calibre bridge file transfer are covered too. - -### Changed -- **The web app's files are cached for a year** (#3177). Build files under `/assets/` carry a content hash in their name, so the browser and any proxy in front of Bindery can keep them without asking again. The app shell, favicon and manifest are still checked every time, so an upgrade reaches you straight away. -- **Audiobook zips build much faster** (#3177). Audio, ebook and image files are stored in the zip as they are instead of being compressed a second time. A zip of an 800 MiB audiobook folder went from about 14 seconds of CPU to well under a second. Text, cue sheets and PDF are still compressed. -- **Metadata lookups keep more connections open** (#3177). Open Library, Hardcover, Google Books and the other metadata providers keep up to 16 idle connections per host instead of 2, so a burst of parallel lookups reuses them instead of reconnecting. diff --git a/changelog.d/api-error-contract.md b/changelog.d/api-error-contract.md deleted file mode 100644 index 8fdc0214a..000000000 --- a/changelog.d/api-error-contract.md +++ /dev/null @@ -1,2 +0,0 @@ -### Changed -- **One JSON error shape across the API.** Unknown paths under `/api` and `/api/v1` now answer `{"error": "not found"}` as JSON, and a wrong method answers `{"error": "method not allowed"}` with an `Allow` header, where they used to be the plain text `404 page not found` and an empty `405`. A request body over the 1 MiB limit answers `413` with `{"error": "request body too large"}` instead of `400 invalid request body`; this applies to the indexer, download client, notification, import list, profile and custom format create and update calls, the genre calls and the manual import calls, and the remaining endpoints follow in a later change. A crash inside a handler is logged with its stack and a request id, so it shows up in Settings, Logs, and the client gets `{"error": "internal server error", "request_id": "..."}` rather than an empty `500`. The browser CSRF rejection and the OPDS Basic auth `429` and `503` are JSON now, and the CSRF one is labelled `application/json` (it was labelled `text/plain`). Lookups that failed because the database was unavailable used to say the row was not found: they now answer `500` instead of `404` for indexers, download clients, notifications, import lists, delay and metadata profiles, custom formats, the book file calls, history blocklisting, pending releases, genre apply and manual import. API change to note: `POST /api/v1/prowlarr/{id}/test` now returns `ok` as a JSON boolean (it was the string `"true"` or `"false"`), matching the indexer test. See the new Error format section in `docs/API.md`. diff --git a/changelog.d/api-error-leaks.md b/changelog.d/api-error-leaks.md deleted file mode 100644 index f3ba76898..000000000 --- a/changelog.d/api-error-leaks.md +++ /dev/null @@ -1,2 +0,0 @@ -### Security -- **Error responses no longer carry database, filesystem or upstream text.** Several API endpoints answered a failure with the raw error string, which could include a metadata provider's request URL with its API key and the resolver address, a SQL driver message, a filesystem path from a failed `stat`, or whatever page a download client or Prowlarr returned. They now answer with a fixed message and keep the detail in the Bindery log. This covers grab and retry of a queue item (and the per row results of a bulk retry or remove), the book rebind and audiobook enrich calls, the author relink calls, add book and adoption, adding a root folder (which still says whether the path is missing, unreadable or under a file), manual import paths and the Fix Match preview, the user role change, the migration uploads and the Audiobookshelf import and rollback calls. Download client and Prowlarr connection tests still say what went wrong, but only the first line, with credentials redacted, at most 200 characters, and an unreachable client is named with the host and port you configured. A paused metadata provider (daily quota or rejected token) still says so. Visible API changes: a grab or retry whose download client refuses the release or cannot be reached answers `502` with `failed to send the release to the download client`; a missing download client is a `400` with `"code": "no_download_client"` (the web app now keys on the code instead of the message text); a download client connection test that fails after reaching the client answers `502` instead of `400`; a rollback of an unknown Audiobookshelf run answers `404` and an unavailable one `503` instead of `400`; changing the role of an unknown user answers `404`; an oversized migration upload answers `413`. A Prowlarr test or sync that fails because the database is down now answers `500` instead of `404`. diff --git a/changelog.d/go-1.26.9.md b/changelog.d/go-1.26.9.md deleted file mode 100644 index 38a08dc78..000000000 --- a/changelog.d/go-1.26.9.md +++ /dev/null @@ -1,2 +0,0 @@ -### Security -- **Built with Go 1.26.9.** The binaries and the container image move from Go 1.26.8 to 1.26.9, which fixes eleven standard library vulnerabilities that `govulncheck` reports as reachable from Bindery's code: nine in `net/http` (GO-2026-6603, 6605, 6609, 6610, 6611, 6612, 6613, 6617) and `net/textproto` (GO-2026-6608), one in `crypto/tls` (GO-2026-6607) and one in `os` (GO-2026-6604). diff --git a/changelog.d/perf-db-queries.md b/changelog.d/perf-db-queries.md deleted file mode 100644 index 29423a200..000000000 --- a/changelog.d/perf-db-queries.md +++ /dev/null @@ -1,5 +0,0 @@ -### Changed -- **Faster book lookups on big libraries.** (#3185) Every book query used to build a table of the first file of each format across all book files before it answered, so opening one book cost the same as a whole library scan. Files are now looked up for the books on the page only. On a test library of 20,000 books and 30,000 files, opening a book went from about 54 ms to 0.13 ms, the first page of the Books list from about 66 ms to under 1 ms, a deep page (offset 10,000) from about 104 ms to 26 ms, and the Wanted list from about 98 ms to 20 ms. -- **History by event type no longer reads the whole table.** (#3185) A new index makes filtering History by event type (and counting those events) instant. On 200,000 history rows, the worst case went from about 220 ms to under 0.1 ms and the page count from 25 ms to under 2 ms. Three smaller indexes cover pending releases per book, the newest-first downloads list (about 295 ms to 261 ms for 50,000 rows with no limit) and the in use check when deleting a quality profile. They are built once at startup: about half a second on a library of that size. -- **Log writes are batched.** (#3185) The persistent log is now written in one transaction per 100 records or 250 ms, whichever comes first, instead of one write per line, so logging takes about 8 times less of the database connection's time (about 108 microseconds a record before, 14 after). After a crash or power loss the last 250 ms of log lines can be missing from the Logs page, and a normal shutdown writes everything out as before. A record that cannot be written is retried on its own and reported once on stderr. -- **Metadata cache eviction no longer scans the whole cache.** (#3185) When the in memory metadata cache is full, making room now looks at a sample of 32 entries rather than every entry while holding the cache lock, so a lookup no longer waits behind a scan of thousands of entries (about 140 microseconds at the 10,000 entry cap before, under 1 after). diff --git a/changelog.d/pollers-retention.md b/changelog.d/pollers-retention.md deleted file mode 100644 index 595c93143..000000000 --- a/changelog.d/pollers-retention.md +++ /dev/null @@ -1,2 +0,0 @@ -### Changed -- **Bindery now deletes finished download rows after 90 days, and the queue, wanted list and download pollers no longer read everything on every request** (behaviour change): every grab adds a row to the downloads table and nothing ever removed one, so the queue page (polled every 5 seconds), the wanted list and each 15 second download poll all got slower the longer an install ran. A scheduled job now removes `imported` and `failed` rows more than `BINDERY_DOWNLOAD_RETENTION_DAYS` days old (default 90, set `0` to keep every row as before). The first pass runs six hours after you start the new version and removes every such row older than the window in one go, which on an install that has run for a year is most of the table, so set `BINDERY_DOWNLOAD_RETENTION_DAYS=0` before you first start the new version if you want to keep them. Rows that are still live or waiting for you (including `importFailed` and `importBlocked`) are never removed, and neither are History, the blocklist or your library. Plainly: once an imported row is gone, Bindery no longer remembers that exact release was grabbed, so if the book becomes wanted again (say you deleted its file) the same release can be grabbed again instead of being refused as already imported. The Queue page also stops listing an `imported` row after 7 days (older ones are on History), while failed and blocked rows stay until you deal with them. `GET /api/v1/wanted/missing` still returns the whole list as a bare array, but now also pages when you pass `limit` (answering `{items, total, limit, offset}`), which the Wanted page uses, and its search now runs on the server. The SABnzbd and NZBGet pollers look their downloads up once per poll instead of once per queue item. From b5e7b6be7a9aa5acaba5093946f4b9bf251545f2 Mon Sep 17 00:00:00 2001 From: vavallee Date: Mon, 12 Oct 2026 00:29:12 -0300 Subject: [PATCH 4/5] chore(release): fold the backfill marker fix into the v1.42.0 changelog Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee --- changelog.d/advanced-tab-backfill-markers.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 changelog.d/advanced-tab-backfill-markers.md diff --git a/changelog.d/advanced-tab-backfill-markers.md b/changelog.d/advanced-tab-backfill-markers.md deleted file mode 100644 index 884657858..000000000 --- a/changelog.d/advanced-tab-backfill-markers.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- **The Advanced settings tab no longer lists Bindery's own startup markers as unrecognised keys.** The five `backfill.*_rev` rows (dedup key, sort keys, book sort key, search key and ASIN case) record which normalizer revision each startup backfill last ran at. They were missing from the settings registry, so the tab labelled them "Unrecognised", said nothing read them, and offered a Remove button. They now show as internal bookkeeping. Removing one is harmless but makes the next start repeat that backfill once, and a new test fails when a backfill marker has no description. From a1877904c1cb3d8fdf27b24cb6f4efcfc237107c Mon Sep 17 00:00:00 2001 From: vavallee Date: Mon, 12 Oct 2026 00:29:24 -0300 Subject: [PATCH 5/5] chore(release): add the backfill marker fix to the v1.42.0 changelog The previous commit removed the fragment but missed the staged entry. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_016fJcCVbNnKsmj2MAAMwWj9 Signed-off-by: vavallee --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dd4776ce..bd9407af2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ This release has two halves. The first is an audit of the API, the database and - **Hardening of the test suite** (#1844 and #3130, #1845 and #3117, #1846 and #3129, #1847 and #3128). The lifetime context, search timeout, scheduler and recommender wiring, and outbound proxy transport tests now exercise behaviour instead of comparing pointers, so they fail when the behaviour breaks. Thanks to francisrath. ### Fixed +- **The Advanced settings tab no longer lists Bindery's own startup markers as unrecognised keys** (#3192). The five `backfill.*_rev` rows (dedup key, sort keys, book sort key, search key and ASIN case) record which normalizer revision each startup backfill last ran at. They were missing from the settings registry, so the tab labelled them "Unrecognised", said nothing read them, and offered a Remove button. They now show as internal bookkeeping. Removing one is harmless but makes the next start repeat that backfill once, and a new test fails when a backfill marker has no description. - **Large audiobook downloads no longer stop at two minutes** (#3177). A book or audiobook download was cut off when it had been running for two minutes, however steadily the client was reading, which hit big files on a slow link. Downloads now run as long as the client keeps reading (one that stops for five minutes is dropped), and resuming with a range request still works. The OPDS download and the Calibre bridge file transfer are covered too. - **Recommendations favour the genres you read, not the ones you read least** (#3172, #3173). The genre profile behind Discover weighed a genre by how rare it was among your genres rather than among your books, so the genre most of your library is in came out negative: books in it were scored down, and it was left out of the genre-based searches. Weights are now never negative, and your main genres count for recommendations. A genre on nearly every book is still discounted. Thanks to francisrath for the report and the fix. - **Hardcover pauses until its daily quota resets** (#2593, #2699). When Hardcover reports no requests left in its daily bucket, Bindery stops sending requests with that token until the reset, keeps the pause across restarts, and shows when the token can be used again in Settings, API Keys. With Hardcover as the primary provider, bulk imports and background catalogue work stop on the hold instead of repeatedly asking an exhausted key. With Hardcover only an enricher, held calls fail at once and ISBN lookups fall through to the other providers. Short per minute throttling is handled as before. Thanks to magrhino for the report and the change.