github vavallee/bindery v1.38.0

4 hours ago

Retry failed downloads, profile ordered releases, a library wide reorganize, and a big round of Hardcover and audiobook fixes

Quality profiles now rank what they allow, so the top format is the one Bindery grabs when a release comes in several. A download that failed can be sent to your client again from the queue, one row or all of them, and an old failure no longer blocks a release from being grabbed automatically forever. Unmonitoring an author finally stops Bindery grabbing their books. Settings, General can restyle your whole library to your naming template in one go, a book page has an automatic search beside the interactive one, and an Advanced tab shows every setting Bindery stores. Requester accounts can be set to approve their own requests up to a daily limit, and audiobooks can be filed by narrator.

On the Hardcover side, fields you locked are no longer overwritten, translated default editions stop hiding English books, and import lists stop looking at other users' libraries. Several audiobook import paths that made a duplicate folder or split one book into many are fixed, and French titles with an elided apostrophe now find their releases. This release adds three database migrations, all additive, which run on their own at startup.

Added

  • Minimum edition count on metadata profiles (#2235): an opt in Min editions floor under Settings, Metadata that keeps thin, one off records out of an author's catalogue when it syncs. A work is judged on the best edition count among the works sharing its title, so one well known record is enough to keep the title. Only OpenLibrary reports edition counts, so the filter only acts on OpenLibrary records: a work with no known count passes, and a Hardcover catalogue is never touched. Books already in your library are never dropped. It is off by default (0). Thanks gchahcg.
  • Retry a failed download, one row, a selection or all of them (#2295): the queue could only retry an import failure, so a grab that never reached the download client, or a download the client gave up on, had no retry at all and had to be found again in search. A failed row now has its own Retry download button, the selection bar gains Retry selected, and Retry all failed covers the download stage as well, so it finally matches the failed count beside it. A retry re sends the release the row already holds; looking for a different release is still the Search button on the book, which is what keeps a fifty row retry predictable. Every multi row retry now goes out as a single request rather than one per row. Thanks ylnest2018 for the report.
  • Reorganize your whole library from Settings, General (#2296): Rename files could already move every tracked file to match your current naming template, but only one author or one book at a time, so there was no way to restyle a library you had just imported. Settings, General, Library now has a Reorganize Library button next to Scan library, and it shows the same preview of every proposed move before anything touches disk. It sits below the scan on purpose: reorganize only moves files a scan has already attached to a book, so scan first. Thanks ylnest2018 for the report.
  • Automatic search for a single book (#2668): the Search button on a book page is the interactive one, so it hands you a list of releases and grabs nothing. A book you wanted Bindery to handle by itself had to wait for the scheduled sweep or be selected on the Books page. There is now an Automatic search button beside it that runs the same search the sweep runs: Bindery picks the best release and sends it to your download client, with the result in the Queue and in History. It appears only while the book still needs a format, and when automatic grabbing is off it says nothing was searched and names the setting instead of looking like it worked. Thanks ThatDeltaGuy for the request.
  • Advanced settings tab (#2311): a new admin only Settings tab that lists every setting Bindery stores and lets you edit it, rendered entirely from the settings descriptor registry. Each row shows the key, its type, its default, the values it accepts and its own one line description, so a rare knob no longer needs a hand built control on a curated tab to be reachable. A setting that only takes effect after a restart is marked next to its control rather than explained in a paragraph. Credentials are never displayed, and the ones with a screen of their own stay editable only there. Rows Bindery writes for itself, keys nothing reads any more, and keys Bindery does not recognise at all are shown and labelled rather than offered as editable. Nothing has moved off the existing tabs.
  • {Narrator} naming token (#2717): file and folder templates can now use {Narrator} to file audiobooks by their narrator. It renders the whole credit (Michael Kramer, Kate Reading), and a book with no narrator metadata gets an empty value, so the segment drops just like an empty {Series}. Thanks skywayskase for the request and tunglambk for building it.
  • Auto-approve requests per requester account (#2718): an admin can turn on auto-approve for a single requester on the Users page, and that account's requests are added straight away instead of waiting in the queue. The same requests.max_pending_per_user limit bounds it per day (25 by default): once the account has had that many requests auto-approved since midnight UTC, the next one falls back to the queue for a human. An auto-approved request sends no requestCreated webhook, so skipping the queue also skips the ping. Off by default, and requests already waiting are left for a human. Thanks flaevers for the request and tunglambk for building it.
  • Bindery Bridge 0.6.0 support. The plugin now advertises what it can do on GET /v1/health and Bindery uses only what is advertised, so an older bridge keeps working exactly as before. With a 0.6.0 bridge: Test connection asks whether the Calibre container can actually see your library root and names the push path remap when it cannot, covers reach plugin mode books instead of only calibredb ones, a path problem stops being reported as a metadata problem, and a book Bindery pushed before its metadata improved gets its empty Calibre fields filled in on the next push.
  • Skipped books in the Push all to Calibre report (discussion #1592). The progress modal has a fourth tile and a table naming why each book was left out: not imported, not monitored, no file on disk, or audiobook only with no ebook. A run with nothing to do now says which of those it hit rather than showing three zeroes.

Changed

  • The three ways of connecting Bindery to Calibre or CWA are now told apart in the UI and the docs (discussion #1355): Calibre only knows about books recorded in its metadata.db, so a file Bindery places in the Calibre library folder never shows up there. The Calibre settings tab now says so up front and names the three hand offs (register each import through calibredb or the Bridge plugin, mirror ebooks into a CWA ingest folder, or let CWA or Calibre own the library from a drop folder in External import mode), the mode descriptions say what each does on disk, and a new Calibre integration guide compares them and walks through "grabbed but never in Calibre". The deployment guide's hand off section pointed at a settings path that does not exist and called pair gating a follow up; both are corrected. Thanks Kedryn for the question.
  • Quality profiles rank releases, top is best (#2733, #2333): a profile is now two ordered lists, ebook formats and audiobook formats, and when more than one ticked format is found the one nearest the top of its list wins, on the scheduled sweep and on the book page alike. A release that carries several formats counts as the best ticked one it carries. Existing profiles are reversed once on upgrade so a list you never reordered keeps preferring what it always did. Thanks stanko0704, ericsilberberg and yarafie for asking which end was best.
  • Colon-separated subtitles read naturally in library filenames. A book titled "System Design Interview: Volume 2" used to be organized to System Design Interview- Volume 2.epub; the colon that separates a title from its subtitle now renders as " - ", so the same book lands at System Design Interview - Volume 2.epub. The OpenLibrary-style "Title : Subtitle" spelling normalizes the same way. A colon without any adjacent space ("A:B") is unchanged. Files already on disk keep their current names; the next Rename files or Reorganize Library run lists every book whose title has a subtitle colon as a move. A download that partly imported before you upgrade and is retried afterwards can import a second copy beside the first, because the destination name changed. Thanks ngoc-minh-do for the change.
  • Hints that explain the workflow where people get stuck: the settings and pages that generate most support questions now say what they do in the terms people ask. Scan Library says it attaches files to books you already have and never creates books, and points at Import for the rest. The wanted search interval says what searches straight away and what waits for the sweep, and that the automatic grabbing switch under Metadata Profiles governs both; that switch says it is one switch for the whole instance. Storage says the download folders are the client's and the library folders are Bindery's. Default media type says how to change existing books and authors. Metadata profiles say they filter the catalogue, not releases, and where the release language filter lives. The Import page says which of its two views is for files already in your library and which is for files elsewhere. The status legend on Books now explains each label instead of only showing the colours, and the pill on a book's page carries the same sentence. Refresh on an author says it re fetches the catalogue and downloads nothing. The generic download client path remap help now shows the from and to order with an example and a Windows path.
  • Quality profile hint no longer contradicts itself (#2733): it said the list was ordered worst to best and that higher in the list wins, and neither was true: the order is saved but read by nothing, and releases are ranked by a fixed built in order. The hint and the user guide now say that, and list the ranking. Translated copies of the old hint were removed so they fall back to the corrected English until retranslated. Thanks stanko0704 and ericsilberberg for asking.

Fixed

  • The queue now says when its list is incomplete (#2376): when a download client did not answer inside its deadline the API already flagged the response as partial, and the page threw that away, so one unreachable qBittorrent rendered as a short or even empty queue that read as lost downloads. The page now names the client it could not reach, above the list.
  • A refused import retry explains itself (#2295): the conflict response read "download is not in importFailed state" whatever state the row was really in, and never mentioned importBlocked, which the same endpoint accepts. It now names the row's state and both states it takes, and points a failed download at the retry that suits it.
  • The bulk search refusal pointed at the wrong settings tab: when automatic grabbing is off, the notice on the Authors, Books, author and Wanted pages told you to turn it on under Settings, General. The toggle lives on the Metadata Profiles tab and has since it moved there, so the notice now names that tab.
  • A second copy of a book no longer claims its neighbour in the series diff (#2410): when a series held one book twice, such as an ebook row and an audiobook row of The Way of Kings, the Hardcover diff bound the second copy to a similar title like The Way of Kings Prime. Prime then showed as owned and was left out of series fill. A row with the same title as a book that already bound, numbers included, is now listed as Local only and Prime stays in Missing. A book whose closest match went to a different book still moves on to its next best entry as before, so owned volumes of a series without stored positions are not listed as Missing and added again by series fill.
  • A series of separate books imported as one audiobook (#2672): Import, From a folder decided a folder was a single multi disc audiobook whenever every one of its audio subfolders had a number in its name, and the list of names it accepted included Book, Vol, Volume and a bare 1 or 02. A series laid out as Mistborn/Book 1, Mistborn/Book 2, Mistborn/Book 3 was therefore offered as a single row named after the series, and importing it attached three books' audio to one book while the other two got nothing. Numbered folders directly under an author collapsed the whole author the same way. Only names that can be a piece of one recording still group (CD, Disc, Disk, Part, Pt, Chapter, Ch), so a real disc set is still one row and a series is one row per book. A disc set stored in bare numbered folders (1, 2) now shows one row per folder; rename them CD1, CD2 to import it as one book.
  • An audiobook imported as a single file still made a Title (2) folder (#2686): with BINDERY_LIBRARY_DIR and BINDERY_AUDIOBOOK_DIR pointing at the same path, an audiobook for a book that already has an ebook is meant to land in that book's existing folder. It did when the import source was a folder of tracks, but a source that is a single file, the ordinary shape of a lone .m4b and of a manual import of one audiobook file, went to a sibling Title (2) folder instead, and that is the path the book recorded. Both shapes now merge. A file already in the book's folder is skipped rather than overwritten, and is named on the import's History entry. Flatten multi disc audiobooks, a per-file audiobook naming template, and a download whose audiobook files do not share a folder of their own still place a separate folder, as documented. Thanks flaevers for the report.
  • A failed download no longer blocks that release for good (#2710): automatic search checked only whether the queue held a row for the release's GUID, so any row stopped the grab, including one that had failed months earlier. Twenty five of the reporter's rows had died on a loopback URL refusal from before BINDERY_DOWNLOAD_ALLOW_LOOPBACK was set, and on indexer 429 and 500 responses during one bad afternoon; all of those causes were long gone, but every re grab was dropped and deleting the queue rows by hand was the only way out. A single transient 429 poisoned a release permanently. Now a failed row stops blocking six hours after it failed, and the next sweep reuses that same queue row rather than writing a second one. The six hours are counted from the failure itself: downloads never recorded when they died, so a row is now stamped at the moment it fails and existing failed rows are backfilled on upgrade. Everything else still blocks: anything in flight, an import into a book you still have, and an import that is blocked, whose files are still on disk and which keeps its Retry import button and its manual Grab rather than being re downloaded unattended. Clicking Grab yourself is unchanged and still retries immediately. The skip is also no longer silent: the search now logs the release, its GUID, the blocking row's id and status, and the reason, and the "book search finished" line names the same reason instead of a bare "already grabbed". Thanks to ccarpinteri for the report and the diagnosis.
  • Multi-file audiobooks on a library scan (#2716): a scanned book folder now records the folder as the audiobook, so every track in it moves or deletes with the book instead of only the first track that matched. Thanks flaevers for the report and tunglambk for the fix.
  • Calibre import aborting on an odd series_index (#2720): a library holding the empty string in series_index failed the whole import with a scan error before a single book was read. Calibre declares that column as a number but SQLite stores a type per value, so a value written by an older Calibre or a third party tool can be text. An unreadable position now reads as no position, and a NULL title, sort or path on the same row no longer aborts the run either. Thanks phoenix1589 for the report.
  • Immediate search when a book becomes wanted and monitored (#2722): the README promises a search as soon as a book is added or flips to wanted, but only a manual PATCH that changed the status started one. A Hardcover list sync that created or widened a book into wanted started nothing, and neither did monitoring a book that was already wanted, so it waited for the next wanted sweep (6h by default). Both paths now fire the same one-off search the PATCH path does. A book that was already wanted and monitored is left to the sweep, so re-syncing a list doesn't search again. Thanks tunglambk for the fix.
  • Disc-split audiobooks on a library scan (#2723): an audiobook stored in CD1/CD2 subfolders under its book folder now reconciles to that book and is recorded as the book folder, so every disc moves and deletes with it. Before, each track took the disc folder for its title and was reported unmatched. Thanks tunglambk for the fix.
  • Unmonitoring an author now stops Bindery grabbing their books (#2742): the author's monitored flag was read nowhere in the grab path. It only fed a cascade that rewrites each book's own monitored flag, and the Authors page bulk Unmonitor never ran that cascade, so unmonitoring 200 imported authors left every book under them monitored and the sweep kept downloading. The scheduled sweep and the automatic re search after a stalled download now skip any book whose author is not monitored, whatever the book's own flag says, and say so in the log. A search you asked for by hand still runs: the book page, the Wanted and author bulk searches, series fill, an accepted recommendation and an explicit add with search on add are all untouched. Bulk Monitor and Unmonitor on the Authors page also gained the "apply to existing books" option the single author path has, unticked by default, and the Wanted page now says when a row is sitting there because its author is not monitored. Thanks ThatDeltaGuy for the report.
  • make test on a case insensitive filesystem (#2752): two download path tests assumed the filesystem could tell books and Books apart, so the suite failed for contributors on macOS. They now probe the filesystem they are running on and assert what it should do there. Test only, no change to how Bindery validates paths. Thanks magrhino for the report.
  • Hardcover audiobook duration (#2755, #2756): use a known audio edition runtime before searching without prioritizing it over stronger audio metadata or promoting print formats; preserve locked languages and let metadata refresh replace whitespace-only edition text fields. Thanks magrhino for the report and the fix.
  • A locked language survives Hardcover edition hydration (#2757): clearing a book's language by hand locks the field, and hydration was filling the empty value from the Hardcover audio edition anyway, so the provider's language came back on the next hydrate. A locked language now stays exactly as it was left, empty included, while an unlocked empty language is still filled from the edition. Thanks magrhino for the report and tunglambk for the fix.
  • Elided titles now match releases that separate the apostrophe (#2762): a title such as L'Outsider, L'Institut or l'isola folded its apostrophe away into a single token (loutsider), while release names in those languages keep it as a separator (Stephen.King.L.Outsider.2018.FRENCH), so the search returned zero results for a release the indexer had answered with, and said nothing, in the interactive search and in auto-grab alike. The strict fold still runs first and unchanged, so English possessives (Ender's Game matching Enders.Game) keep working; a second reading that treats the apostrophe as a separator is tried only when the first finds nothing, at every point that judges a release: the indexer response gate, the interactive and auto-grab filter, and the searcher's own relevance filter. Thanks misterk72 for the report and the fix.
  • One "Book imported" history row per download (#2764): a release carrying several ebook formats for one book wrote a row per file, so an NZB holding .azw3, .epub and .mobi left three identical rows with the same release title and the same timestamp and nothing to tell them apart. The row is now written once per download, where the bookImported webhook and the audiobook import already do it, and it names the formats that arrived, which is the part worth reading. The History page shows that list ahead of the path. A partial import, where some files landed and some did not, still writes no imported row: the download stays retryable and the existing "partial import" row says how many files failed. Rows written by older versions are unchanged and still render the way they always did.
  • A Hardcover import list no longer decides what to create by looking at other users' libraries (#2766). The list sync stamped the list owner on every book and author it created, but the reads that decided whether to create at all were instance wide, so with BINDERY_ENFORCE_TENANCY on a list could skip a book because a different user already had it, widen that user's book to both formats and re-open it as wanted, or park its books under an author belonging to somebody else. Those reads are now scoped to what the list owner can see, which is their own rows plus unowned ones. Books and authors with no owner stay shared and reusable by every list. A work whose Hardcover id another user already holds still cannot be created twice, since those ids are unique across the instance, but the sync now leaves that row alone and logs why it skipped instead of rewriting it silently. This only changes behaviour on installs running with tenancy enforcement on. With the flag off, which is the default, every user shares one library view and the sync behaves exactly as before.
  • Hardcover hydration and metadata enrichment stop overwriting fields you locked (#2767): editing a book's description or genres by hand locks the field, but the Audnex audiobook enrichment and the provider enrichment merge wrote over it anyway, so a hand written description came back as the provider's on the next refresh and a locked genre list was replaced by Hardcover's taxonomy. Clearing a description and locking it was the worst of it, because an empty value looked like a gap to fill. Every one of those writes now asks whether you own the field first, on the cached replay as well as the live path.
  • Re-bind and explicit adds stop widening a media type you chose (#2768): the Hardcover edition hydration that runs after those actions never carried the "this format was chosen, not guessed" pin the series fill already passed, so a book you set to ebook could come back as both, with the audiobook's ASIN attached, just because the work has an audio edition somewhere on Hardcover. Those callers now forward the pin. A format that came from the provider or from the default setting is still widened as before, and that includes an accepted recommendation, whose format the recommender picks rather than you. Thanks tunglambk for the fix.
  • The Calibre contract test now proves the degradation it advertises (#2778): the test says it covers graceful degradation against calibre-bridge 0.5.0, but against a 0.5.0 bridge three rows only checked a boolean and one asserted nothing at all, while the wire level behaviour was pinned only against hand written fakes. It now drives the real 0.5.0 handler through a recording proxy and asserts what Bindery actually sends: no coverPath field, no PATCH, no path probe, and the legacy payload retry firing only on a 400 that carries no error code. The harness also asks the bridge's make_handler which keyword arguments it accepts instead of assuming, so a checkout older than the released 0.5.0 skips with a message naming the missing arguments and the version that checkout reported, rather than failing as "harness exited without reporting a port". Test only, no runtime change. Thanks magrhino for finding the coverage gap.
  • Genres read from the shared metadata cache are no longer shared with the book that reads them (#2783): the enrichment cache stored a book's Genres list by reference and handed that same list to every later book for the work, so editing one of those lists in place would have changed the cached copy too. The cache now keeps and returns its own copy. Thanks tunglambk for the fix.
  • A series linked from a provider id no longer claims a perfect match (#2784): a Hardcover series link created from a provider-supplied hc-series: id recorded confidence 1, the same value a link you confirmed against the catalogue earns, even though nothing about it had been matched or scored. It now records 0.8, which is what the other unscored auto paths already use, so the two stay distinguishable. Thanks tunglambk for the fix.
  • Changing Calibre settings needed a restart. The Calibre client was built once at startup from the mode in force at the time, while the mode itself was read live. Switching mode in the UI, or correcting the plugin URL, the API key or the push path remap, did nothing until Bindery restarted, and the resulting push was a silent no-op with nothing in the log. Both are now resolved per push. Thanks to everyone in #1355 who reported fixing the remap and seeing no change.
  • Push all to Calibre sent thinner metadata than a live import. A bulk pushed book arrived with no series, series index, description, publisher, published date or rating, because the two push paths built their payload separately. They now share one builder.
  • Audiobook imports sent a folder to an endpoint that takes a file. In plugin mode every audiobook import produced two rejected requests and a misleading add failed warning; in calibredb mode it quietly added nothing, because Calibre's book extension list carries no audio format. Neither is attempted now. The Calibre integration guide described this as a working feature and no longer does.
  • A mount problem was reported as a metadata problem. A 400 from the bridge triggered a full retry with a path only body and warned about the metadata payload, even when the real cause was a path the Calibre container could not open. The retry is now limited to rejections that are actually about metadata, and the warning names both possibilities.
  • A 503 gave up after one flat two second retry, where the protocol specifies exponential backoff to about thirty seconds. It now backs off 1s, 2s, 4s, 8s and 15s before giving up on a book.
  • Upgrading the plugin under a running Bindery had no effect until Bindery restarted, because the capability probe was cached for the life of the process. It now expires after five minutes and is dropped on any transport error.
  • Star ratings could differ by half a star between the two modes. Calibre truncates the rating it gets through calibredb, the plugin rounds the one it gets over HTTP. Bindery now sends a value already on a half star boundary, so both land on the same number of stars.
  • The plugin log could not tell which Bindery version it was talking to. Requests now carry the version in the User-Agent, as the protocol asks.
  • Hardcover author language filtering (#2753) uses bounded edition evidence instead of treating a translated default edition as the language of the whole work, and keeps unresolved reconciliation rows safe. Thanks kevinatlee for the report and the fix.

Docs

  • Documented how an author's name decides the letter it files under (refs #2805): the Authors list, the OPDS author feed and the {SortAuthor} naming token all order by the author's sort name, which Bindery writes as "Last, First", so the Sort menu's plain A to Z entries are already a last name sort. The user guide now has a table of worked examples and states the rule that settles the hard cases: where the particle tables cannot decide, case does, so "Vincent van Gogh" files under G while "Thomas De Quincey" files under D, French Le and Du lead whatever their case, a compound like "de la" moves as one unit, a generational suffix follows the forename, and a single word name, a name that already carries a comma and a name written entirely in CJK script are all left exactly as they are. The limits are written down too, including that a CJK name in Latin letters is read forename first and that the sort name cannot be edited by hand anywhere in the UI or the API, which is now also stated on the metadata editing page next to the fields you can lock.
  • Every documentation page was checked against the code and corrected: the upgrade guide named the wrong multi-user migration throughout (it is 025_multiuser.sql, not 019), told you to watch for a log line Bindery does not emit and to run a --dry-run flag that does not exist, and looked for backups under a filename pattern that matches nothing. The telemetry opt out, the backup panel, the Hardcover API token, the auto grab switch and the Users page were all documented at settings paths they have since moved away from, in several cases by three pages agreeing with each other and disagreeing with the app. The user guide still described the four value book status chain that lost two values in v1.34.0, a minimum popularity filter removed in v1.35.0, a 100 title catalogue cap lifted in v1.22.0, an Exclude action the Books page does not have and a Queue manual import screen that does not exist. README said OPDS takes the API key as an HTTP Basic password, which it does not, that library scan matching is four tier when it is five, and that four webhook events ship when there are seven. The deployment guide listed a container tag that is never pushed, a Helm example that renders no ingress, a shutdown grace default the duration parser rejects, and a shared ebook and audiobook folder layout the importer cannot produce. The roadmap still listed unattended release discovery as in flight two releases after it shipped. Also added: health checks and probes, Docker Hub and the arm64 manifest, the fourteen day log retention, the blocklist, the Diagnose button, why the bulk Search buttons refuse when automatic grabbing is off, and the plugin duplicate rule that makes a first Push all into an existing Calibre library add everything twice.

Don't miss a new bindery release

NewReleases is sending notifications on new releases.