SoftScanAllLibraries compares audio file count on disk vs DB track count
per library. Unchanged libraries are silently skipped (no progress bar,
no events). Only libraries where files were added or removed since the
last scan get queued for a full scan.
recording_genres and release_group_recordings reference recordings.id,
so they must be deleted BEFORE the recordings table is cleaned.
Also extends toast duration to 8s for readability.
RemoveLibrary now polls until the cancelled scan goroutine finishes
before proceeding with the removal transaction. Also show removal
errors as toast messages instead of only logging to console, and
explicitly reload library list after successful removal.
Tracks from pre-multi-library schema have library_id=0 (default).
When AddLibrary creates a new library row, UPDATE orphaned tracks
whose file_path falls under the library's directory to use the new
library_id. This prevents the scan from hitting UNIQUE constraints
on every file and ensures track counts are correct immediately.
- Add checkboxes to library list with select-all header
- Soft Scan operates on selected libraries (queues each individually)
- Full Rescan stays global (nukes all data, rescans all libraries)
- Remove redundant 'Scan All Libraries' button
- Fix FullRescan Go backend to scan all libraries after wipe, not just first
Files that fail to save (e.g. UNIQUE constraint from pre-existing
tracks with a different library_id) were not counted in any progress
counter, leaving the progress bar stuck at 0%. Now increments skipped
so processed = added + skipped + updated reflects all files visited.
Task 1: Schema, events, and progress types
- Add library_id to CreateAudioFile SQL INSERT and regenerate sqlc code
- Add LibraryScanQueued and LibraryScanQueueDrained event constants
- Regenerate TypeScript events via genevents
- Add LibraryID, LibraryName, QueuedCount to ScanProgress
- Add LibraryID, LibraryName to ScanMetrics
- Add libraryID field to importResult for threading through pipeline
Task 2: Scan queue coordinator and per-library scanning
- Create scan_queue.go with ScanLibrary(id), ScanAllLibraries()
- Add CancelCurrentScan(), CancelAllScans() for queue-aware cancellation
- FIFO scan queue with silent dedup (same library already scanning or queued)
- Refactor Scan() -> scanInternal(libraryID, libraryName, libraryPath)
- Replace GetAllAudioFiles with GetAudioFilesByLibrary for per-library loading
- Thread libraryID through DB writer to set CreateAudioFileParams.LibraryID
- drainQueue auto-starts next queued library or emits LibraryScanQueueDrained
- Pause freezes current scan AND queue
- Add GetScanQueueLength() and QueuedLibraryNames() for UI
- Mark CancelScan() and Scan() as deprecated
- Create backend/shortcuts/config.go with DefaultBindings(), ApplyDefaults(), Validate()
- Wire Shortcuts field into main Config struct with TOML persistence
- Add GetShortcuts, SetShortcuts, SetShortcut, ResetShortcuts Wails binding methods
- Regenerate Wails TypeScript bindings for new config methods
- Fix wsl lint in library.go (blank line before logger call)
- Add scanActive, scanCancel, scanPaused, scanPauseCh fields to Library struct
- Create scan_control.go with CancelScan, PauseScan, ResumeScan, IsScanActive, IsScanPaused
- Thread per-scan scanCtx through walk and worker pipeline
- Add waitIfPaused checkpoint before each worker extraction
- Skip orphan cleanup and variant generation on cancelled scan
- Emit LibraryScanCancelled instead of LibraryScanComplete when cancelled
- Add LibraryScanCancelled, LibraryScanPaused, LibraryScanResumed events
- Regenerate frontend/src/events.ts via go generate
- Add Cancelled bool field to ScanMetrics struct
- Fix errcheck for db.Close() in testhelper.go
- Fix errcheck, nlreturn, wsl, gofumpt issues in genevents/main.go
- Fix gofumpt and wsl issues in library.go
The GetAudioFilesByReleaseGroup SQL query only selected 6 columns,
missing audio properties (sample_rate, bit_depth, channels, bitrate,
file_size) and metadata (album, genre, year, composer, file_type).
This caused track details opened from the album view to show dashes
instead of actual values. Expanded the query to match
GetAllTracksWithFullMetadata and updated GetAlbumTracks to use the
shared mapTrackRow helper.
Add live progress reporting during library scans:
- Pre-walk count: fast WalkDir to count audio files upfront for
percentage calculation (~1-2s overhead)
- Progress ticker: emits ScanProgress events every 300ms with
phase, file counts (added/skipped/updated), and total
- Phase labels: counting → scanning → thumbnails → orphans
- Frontend: progress bar with percentage, file counts breakdown,
and phase indicator in both config-page and library-manager
Replaces the static 'Scanning...' text with a live progress bar
showing e.g. '62% — Scanning... 1,247 / 2,013 files (891 new,
356 skipped)'
The search_index is a contentless FTS5 table (content=''), which
SQLite does not support DELETE on. ClearSearchIndex now drops and
recreates the virtual table. Single-row DeleteSearchIndex becomes
a no-op since contentless FTS5 also cannot delete individual rows;
stale entries are harmless (search JOINs filter them out) and the
index is fully rebuilt during FullRescan.
- Migration 5 rebuilds release_groups with UNIQUE(name, album_artist_credit_id)
- Drops and recreates track_metadata VIEW during table rebuild
- Temporarily disables FK checks for safe table rebuild
- Entity cache now keys by album name + artist credit ID
- Update tests to use composite cache keys
- 7 SAFETY comments in search.go (FTS5 MATCH/INSERT/DELETE operations)
- 3 SAFETY comments in library.go (FTS5 INSERT/DELETE in commitNewAudioFile, updateAudioFileMetadata)
- 1 SAFETY comment in rescan.go (FTS5 DELETE in clearAllLibraryData)
- 1 SAFETY comment in persistence.go (variable-count multi-row INSERT)
- Cross-references link library.go/rescan.go back to search.go
- Two-part format: why sqlc can't handle it + what makes it safe
- TestCachedUpsertArtistCredit: cache hit returns same ID on second call
- TestCachedLinkArtist: skips duplicate INSERT via linkedCredits cache
- TestCachedLinkArtist_MultiCredit: same artist linked to different credits
- TestCachedUpsertGenre: genre cache returns same ID on repeated calls
- TestResolveReleaseGroup: creates release group, updates cover art on cache hit
- TestResolveReleaseGroup_CacheHit: pre-populated cache returns cached ID
- TestOrphanDeletion: DeleteAudioFile removes row, documents contentless FTS5 limitation
- TestEntityCache_EmptyFields: empty artist name, empty album, AlbumArtist reuse
- TestGetRecordingName: title present, fallback to filename sans extension
- TestToNullInt64: zero as null, positive/negative as valid
- TestToNullString: empty as null, non-empty as valid
- TestSplitGenres: empty/single/multiple genre splitting on || delimiter
- TestMapTrackRow: all 16 columns including string TrackLength, NullInt64 fields
- Add ScanWarning struct with FilePath/Phase/Err to ScanMetrics
- Add mutex-protected addWarning method for concurrent use
- Reclassify walk, extraction, commit, orphan, variant, FTS failures as warnings
- Update commitBatch to return only fatal tx.Commit errors
- Update cachedLinkArtist to check errors via database.IsUniqueViolation
- Update handleConfigUpdate to capture and log scan warning count
- Queue.SetContext acquires existing q.mu before writing q.ctx
- Library struct gets new mu sync.Mutex; SetContext and SetRescanHooks acquire it
- Playlist Service struct gets new mu sync.Mutex; SetContext and SetFavoritesConfig acquire it
- Library and Playlist release lock before calling post-init methods (registerEventHandlers, migrateExistingPlaylists)
album artist gets populated with artist as fallback (frontend also uses
this as display name fallback for albums). added scroll position
persistence when switching main views. also added frontend cache for
faster switching.
- Fix 10 err113 violations: extract dynamic errors to package-level sentinels
- Fix 12 errcheck violations: handle unchecked error returns in player,
metadata, and config packages
- Fix 4 revive stutter warnings: rename player.PlayerState to player.State,
player.PlayerVolume to player.Volume, queue.QueueTrack to queue.Track,
queue.QueueState to queue.State
- Fix 2 staticcheck SA4001: simplify *&x to x in assets handler
- Fix 5 unused constants: remove dead AudioFileType iota block in models
- Fix gci/gofumpt/wsl formatting issues across multiple files
- Add gofumpt module-path setting to .golangci.yml for correct import grouping
- Fix player test: gate integration test behind YELLOWJACKET_INTEGRATION env var
instead of only skipping in CI, and replace t.Errorf+t.Failed with t.Fatalf
- Remove continue-on-error from golangci-lint CI step so linting is now required
* chore(deps): update go dependencies (non-major)
* fix: regenerate code and add postUpgradeTasks for Renovate
Regenerate templ and sqlc output to match bumped tool versions.
Configure Renovate postUpgradeTasks to run 'go generate ./...' after
Go dependency updates, preventing stale generated files in future PRs.
---------
Co-authored-by: onion-4-dinner <15676555+onion-4-dinner@users.noreply.github.com>
- Default to empty Config in NewLibrary when nil is passed, so Wails
binding generation succeeds without a config file on disk.
- Fix golangci-lint v2 flag in lefthook (--build-tags, not -tags).
- Run golangci-lint on full project instead of individual staged files.