fix(scan): don't attribute every book by an author to whichever one is scanned#766
fix(scan): don't attribute every book by an author to whichever one is scanned#766m4bard wants to merge 1 commit into
Conversation
…s scanned
ScanFileDiscovery.Matches accepted "the path contains the author" as
sufficient to attribute a file to a specific audiobook. In the usual
{Author}/... layout every file beneath an author's folder contains that
author's name, so the clause was true for all of them.
FindMatchingAudioFiles takes every file in any directory group where any
file matches, so each of the author's book folders was claimed whole, and
ScanPathPlanner.CalculateBasePath then reduced the result to its common
parent -- the author folder. That became the audiobook's BasePath, which is
what rename and move operate on.
This is reached whenever an audiobook has no BasePath, because the scan root
then falls back to the configured OutputPath and the whole library becomes
candidates. A BasePath is empty in normal use: it is only assigned on add
when a destination path is supplied, and ScanJobProcessor sets it to null
when the stored folder no longer exists.
The author names a shelf, not a book. Attribution by path now requires the
title, in the filename or in the containing directory. A path carrying
neither the title nor anything else identifying is left unmatched rather
than attached to an arbitrary book by the same author -- a miss is
recoverable and visible, a wrong link is neither.
Layouts that encode neither title nor author in the path go from wrongly
matched to unmatched. Those are what an embedded-tag pass should claim.
No existing test depended on author-only matching.
Fixes Listenarrs#765
|
Followed up with a runtime before/after against the published image, since the unit tests above only cover the matcher in isolation. Setup: a synthetic library generated from public-domain metadata in the common Each scanned book has a same-author sibling elsewhere in the library, so the author-in-path clause has something to wrongly grab:
So the over-attribution is present on One thing that surprised me: the stored The harness is a small generator + scan-diff script; happy to open-source it if that'd be useful for regression-testing scan behaviour more generally. |
Adapt the seven path-attribution scenarios from #766 to the current ScanFileDiscovery.Discover API and boundary-aware BasePath planning. Co-authored-by: m4bard <[email protected]>
… whole shelf to the scanned book Cherry-pick of m4bard's fix (Listenarrs#766, slated for upstream via Listenarrs#717): ScanFileDiscovery.Matches treated 'path contains the author' as attribution to a SPECIFIC audiobook, which is true for every file under the author's folder in the {Author}/... layout — one book's scan could swallow its shelf-mates' files and re-root BasePath at the author folder. Only the title attributes by path now; repro tests included from the upstream PR. Live-relevant: every organize/move enqueues a post-move scan, and ~90 descend-move scans ran on 2026-07-13. Likely origin of multi-book records (Dark Journey holding 'The Shadow'/'Journey of Fate' files). Co-authored-by: m4bard <[email protected]> Co-Authored-By: Claude Fable 5 <[email protected]>
Summary
Fixes #765.
ScanFileDiscovery.Matchesaccepted "the path contains the author" as sufficient to attribute a file to a specific audiobook:In the usual
{Author}/...layout, every file beneath an author's folder contains that author's name — so the clause is true for all of them.FindMatchingAudioFilestakes all files in any directory group where any file matches, so each of the author's book folders is claimed whole, andScanPathPlanner.CalculateBasePathreduces the result to its common parent: the author folder. That is then stored as the audiobook'sBasePath— which is what rename and move operate on.Reached whenever an audiobook has no
BasePath, because the scan root then falls back to the configuredOutputPathand the whole library becomes candidates. ABasePathis empty in normal use: it's only assigned on add when a destination path is supplied, andScanJobProcessorsets it tonullwhen the stored folder no longer exists.Reproduction
Three public-domain books by one author, conventional layout:
Scanning the audiobook She:
Changes
Changed
ScanFileDiscovery.Matchesnow attributes a file to an audiobook only when the title appears — in the filename or in the containing directory. The author is no longer a match on its own.Fixed
BasePathis no longer collapsed to its author's folder.Testing
ScanFileDiscoveryReproTests— 7 new cases against real temp directory trees (FindMatchingAudioFilesis a static taking a root path and anAudiobook, so this needs no database or container):BasePathresolves to the book folder, not the author folder{Author}/{Author} - {Series} - {Title}shape)dotnet test: 1196 passing, 0 failing (1189 existing + 7 new). No existing test changed — nothing in the suite depended on author-only matching.Notes
The trade-off, stated plainly: layouts that encode neither the title nor the author in the path — AudioBookShelf-style series folders with numbered episode files — go from wrongly matched to unmatched.
That is the right direction. A miss is recoverable and visible to the user; a wrong link is neither, because the file is silently attached to a book the user has no reason to go and check. And those layouts are exactly what an embedded-tag pass should claim, which is what #688 proposes — the two changes complement each other rather than overlap.
Books cited are public domain with real Audible editions, so the case is reproducible by anyone.