Closed
Conversation
- New route /library-import with full-page import UX - Pinia store (libraryImport.ts) with FIFO lookup queue, scan, per-row search, and batch import - LibraryImportRow.vue: checkbox, folder path, detected metadata, inline match search with debounce - LibraryImportFooter.vue: sticky toolbar with Start Processing / Cancel / Import buttons and rate-limit warning (>100 lookups) - LibraryImportView.vue: root folder selector, scan button, last-scanned timestamp, results table - Uncomment nav link in App.vue; add PhFolderOpen import - Queue processes one item at a time (naturally rate-limit safe, matching Sonarr's approach) - UnmatchedFilesModal.vue unchanged — additive change Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SearchResult has no 'author' string field — use authors[] only - Narrow lookupQueue[0] with non-null assertion to satisfy strict index checks - Use object spread assignment instead of direct index mutation for items record Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Folder names often contain year prefixes and series brackets (e.g. "2021 - Cleave's Edge [Morcster Chef 1]") which produce poor search results. When file tags provide a clean detectedTitle, use that instead for both auto-processing queue searches and the inline search pre-fill. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ASIN from file tags gives an exact match via advancedSearch({ asin }).
Falls back to detectedTitle, then folderName for title search.
Inline search panel pre-fill follows the same priority.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scans a root folder for audio files not already in the library,
parses metadata from the folder path structure and sidecar files
(desc.txt, reader.txt), and surfaces results for manual review.
Users can add each unmatched book to the library via the existing
AddLibraryModal and optionally link the file via manual-import.
- PathMetadataParser: parses {Author}/{Series}/{Year} - {Title} [{Series} #]/
folder structure plus desc.txt and reader.txt sidecar files
- UnmatchedScanQueueService: channel-based background job queue
- UnmatchedScanBackgroundService: walks root folder, compares against
tracked AudiobookFiles in DB, groups by book folder, pushes
UnmatchedScanComplete via SettingsHub SignalR on completion
- RootFoldersController: POST /{id}/scan-unmatched and
GET /unmatched-results/{jobId} endpoints
- UnmatchedFilesModal: two-phase modal (scan progress → results table
with Add/Ignore per row) launched from root folder card
- RootFoldersSettings: magnifying glass button on each folder card
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds ffprobe-based tag extraction to the unmatched scan pipeline. For each book group, reads album_artist (author), composer (narrator), SERIES, PART, date, description, and ASIN from embedded iTunes/Nero atoms. Embedded tags take priority over path-parsed values. ffprobe calls run in parallel (max 4 concurrent) to avoid overwhelming the NAS. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rvice Add FakeUnmatchedQueue stub and update all controller instantiations to include the new required constructor parameter. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds UnmatchedScanConcurrency setting (default 2, range 1-8) to ApplicationSettings. The unmatched scan reads this at job start via IConfigurationService and passes it to Parallel.ForEachAsync, so users can tune NAS I/O pressure vs. scan speed from General Settings. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Track last completed scan job per root folder path in UnmatchedScanQueueService
- Add CompletedAt timestamp to UnmatchedScanJob
- Add TryGetLastJobForPath() to IUnmatchedScanQueueService interface
- New GET /rootfolders/{id}/unmatched endpoint returns cached results for a folder
- Modal now loads saved results on open (no auto-scan)
- Added explicit Scan button — only rescans when clicked
- Shows 'Last scanned X ago' timestamp on cached results
- Shows empty state with instructions when no scan has run yet
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Controller constructor now requires ListenArrDbContext (added for GetSavedUnmatched filtering). Tests use an in-memory DB via CreateDb(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ManualImport_MultiFileCollisionTests: pass IRootFolderService mock to updated
ManualImportController constructor
- SearchControllerTests + SearchControllerUnifiedTests: advanced search now returns
{ results: [...], totalResults: N } instead of a bare array; update assertions
to unwrap the results property before checking array length and elements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ok.FilePath Books imported via manual import only have Audiobook.FilePath set — the AudiobookFiles table is empty for them. The scanner was only checking AudiobookFiles, causing already-imported books to appear as unmatched and triggering a 409 on add. Now checks both sources. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
UnmatchedFilesModal addAllWithAsin now passes destinationPath so BasePath is set correctly on the created audiobook (fixing silent import failure). GetSavedUnmatched filters cached results against current library state so re-opening the modal no longer shows already-added items. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… wording - UnmatchedScanBackgroundService: files directly in the root folder (flat layout) now each produce their own result entry. Previously all flat files shared the same parent-folder group key → one entry with one representative file's metadata (and fileCount = N). Now each flat file is its own group; bookFolder falls back to the actual parent directory for the import path. - UnmatchedFilesModal: eagerly load applicationSettings + rootFolders on open so the 'Move to:' / 'Copy to:' label and destination dropdown always reflect the user's configured file action and available root folders. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…youts
Replace flat-root special case with a general two-level grouping strategy:
1. Group files by parent directory (folder = one audiobook, as before)
2. Within each directory that has multiple files, sub-group by a normalized
title stem derived from the filename (strip track numbers, Part/CD/Disc
suffixes, year/series brackets, normalize whitespace)
- Same stem or all-empty (numbered parts: 1.m4b, 2.m4b) → one audiobook entry
- Different stems (author folder with multiple books, flat root with
distinct titles) → separate audiobook entries per title
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- matchToMetadata: extract series name/position/asin from AudimetaSeries[]
(advancedSearch returns series as array; AudibleBookMetadata expects string)
This was causing import failures when a matched book had series data
- searchItem: detect 10-char ASIN pattern and pass as { asin } not { title }
so manual search works when user enters or pre-fill contains an ASIN
- LibraryImportRow: display series name correctly in results dropdown
(was showing raw JSON array object)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…oot folder ManualImportController.GenerateManualImportPathAsync was treating any BasePath that differed from settings.OutputPath as a 'custom' destination, applying only the file naming pattern and placing files flat into that folder with no subfolder hierarchy. This broke batch adds from UnmatchedFilesModal when the selected root folder wasn't the same path as OutputPath. Fix: inject IRootFolderService and check whether BasePath matches any configured root folder. If it does, isCustomBasePath = false → full folder+file naming pattern is applied → files are correctly organized into Author/Title/File.m4b. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…or UnmatchedScanConcurrency Register IUnmatchedScanQueueService singleton outside the DisableHostedServices guard so RootFoldersController can be resolved in tests. Add UnmatchedScanBackgroundService to HostedServiceRegistrationExtensions. Add missing EF migration for the UnmatchedScanConcurrency property on ApplicationSettings, which was causing PendingModelChangesWarning to abort migrations at startup in the test host, leaving all tables uncreated and causing 10 test failures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace static 'Move to:' / 'Copy to:' label with dropdown-first layout showing the actual root folder path: '[Move ▼] to: /path/to/folder'. Path truncates with ellipsis and full path on hover. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hared folders Files like "The Land (1).m4b", "The Land (2).m4b" in one folder were all reducing to the same stem "the land" and being grouped as one multi-part audiobook. They are actually separate books in a series. Changes to ExtractTitleStem: - Keep plain numeric parens (1), (2) etc. — these distinguish series books - Only strip 4-digit year parens (2020), (2021) - Only strip square bracket content [Series Name], [Chaos Seeds 1] - Add 'pt' to the trailing part-number strip so "pt00" is removed (pt00 = whole book, never increments — safe to strip) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the static path label with a <select> showing all root folders, letting users choose where to import files independently of the scan folder. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…earch
- importSelected: when addToLibrary returns 409 (book already in library),
extract the existing audiobook from the conflict response body and continue
with startManualImport — fixes import failure for Missing books
- buildSearchTitle: extract trailing number from filename (e.g. "The Land (3).m4b")
and append to the search query ("The Land 3") so series book numbers resolve better
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When addToLibrary returns 409 (book already in library), the existing audiobook may have BasePath=null or pointing to the wrong location. Update BasePath to the selected destination folder before calling startManualImport so the naming service generates the correct path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ismatches - pickBestMatch: when detectedAuthor is available, prefer the search result whose author matches over just taking results[0] - LibraryImportRow: show matched author in orange when it doesn't match detectedAuthor, with tooltip showing the detected author Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…thors/narrators Search results return authors without names; fetch full Audimeta data at import time (same pattern as UnmatchedFilesModal). Also fix v-if guards in detail view to hide author/narrator rows when arrays are empty rather than showing blank labels. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Owner
Author
|
test PR, closing |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
test