diff --git a/backend/app.go b/backend/app.go index f7b789a..867d0ca 100644 --- a/backend/app.go +++ b/backend/app.go @@ -275,12 +275,12 @@ func (yj *YellowJacketApp) OnDomReady(ctx context.Context) { return } - // Auto-scan all libraries on launch. Runs in a goroutine so - // it does not block the DOM-ready callback. Uses the same - // ScanAllLibraries codepath as the UI button. + // Soft scan: compare file counts on disk vs DB for each library. + // Only libraries with mismatched counts get a full scan — unchanged + // libraries are silently skipped (no progress bar, no UI noise). go func() { - if err := yj.library.ScanAllLibraries(); err != nil { - yj.logger.Error("auto-scan failed", "err", err) + if err := yj.library.SoftScanAllLibraries(); err != nil { + yj.logger.Error("soft scan failed", "err", err) } }() } diff --git a/backend/library/scan_queue.go b/backend/library/scan_queue.go index bc2a348..62ff60a 100644 --- a/backend/library/scan_queue.go +++ b/backend/library/scan_queue.go @@ -90,6 +90,69 @@ func (l *Library) ScanAllLibraries() error { return nil } +// SoftScanAllLibraries performs a lightweight launch-time scan. +// For each library it compares the number of audio files on disk +// against the track count in the database. Only libraries where the +// counts differ (files added or removed since last scan) are queued +// for a full scan. Libraries that are unchanged are silently skipped, +// producing no progress-bar UI noise. +func (l *Library) SoftScanAllLibraries() error { + libs, err := l.db.Queries.GetAllLibraries(l.ctx) + if err != nil { + return fmt.Errorf("could not get all libraries: %w", err) + } + + for _, lib := range libs { + dbCount, countErr := l.db.Queries.CountAudioFilesByLibrary( + l.ctx, lib.ID, + ) + if countErr != nil { + l.logger.Warn( + "soft scan: could not count DB tracks, queueing full scan", + "libraryID", lib.ID, + "libraryName", lib.Name, + "err", countErr, + ) + + _ = l.ScanLibrary(lib.ID) + + continue + } + + diskCount := countAudioFiles(lib.Path) + + if diskCount == dbCount { + l.logger.Info( + "soft scan: library unchanged, skipping", + "libraryID", lib.ID, + "libraryName", lib.Name, + "tracks", dbCount, + ) + + continue + } + + l.logger.Info( + "soft scan: file count mismatch, queueing scan", + "libraryID", lib.ID, + "libraryName", lib.Name, + "diskFiles", diskCount, + "dbTracks", dbCount, + ) + + if err := l.ScanLibrary(lib.ID); err != nil { + l.logger.Warn( + "soft scan: could not queue library", + "libraryID", lib.ID, + "libraryName", lib.Name, + "err", err, + ) + } + } + + return nil +} + // CancelCurrentScan cancels only the currently scanning library. // The next queued library (if any) starts automatically when the // current scan's goroutine completes.