fix: start index build only after ALL library scans complete

FullRescan scans the first library directly, then queues the rest.
The PostScan hook was restarting the index build after the FIRST
library, which starved the queued libraries for DB access — they
never scanned, leaving the library with only 6 tracks.

Fix: move StartIndexBuild to the OnAllScansComplete hook, which
fires when drainQueue finds no more libraries to scan. This ensures
ALL libraries finish scanning before the index build starts.

For startup soft scans: if no scans were queued (library unchanged),
start the index build directly. If scans WERE queued, the hook
handles it.

Added OnAllScansComplete callback to ScanHooks. Called from
drainQueue when the scan pipeline goes idle.
This commit is contained in:
2026-03-28 10:25:04 -04:00
parent 9a841cd173
commit 3c3102aac6
3 changed files with 21 additions and 5 deletions
+13 -5
View File
@@ -206,8 +206,10 @@ func (yj *YellowJacketApp) OnStartup(ctx context.Context) {
},
PostScan: func() {
yj.playlist.RestoreAllPlaylists()
// Restart the index build now that the scan is done.
yj.explore.StartIndexBuild()
// DON'T restart the index build here — queued
// library scans may still be running. The index
// build starts after ALL scans complete (via the
// scan hooks below).
},
})
@@ -215,6 +217,9 @@ func (yj *YellowJacketApp) OnStartup(ctx context.Context) {
// phantom tracks after each library scan completes.
yj.library.SetScanHooks(library.ScanHooks{
ResolvePhantoms: yj.playlist.ResolvePhantomTracksAfterScan,
OnAllScansComplete: func() {
yj.explore.StartIndexBuild()
},
})
// Wire removal hooks so the library can stop playback and
@@ -329,8 +334,11 @@ func (yj *YellowJacketApp) OnDomReady(ctx context.Context) {
yj.logger.Error("soft scan failed", "err", err)
}
// Start the explore search index build AFTER the library
// scan completes so they don't fight for DB access.
yj.explore.StartIndexBuild()
// If no scans were queued (library unchanged), start the
// index build directly. If scans WERE queued, the
// OnAllScansComplete hook starts it after they finish.
if yj.library.GetScanQueueLength() == 0 && !yj.library.IsScanActive() {
yj.explore.StartIndexBuild()
}
}()
}
+3
View File
@@ -79,6 +79,9 @@ type ScanHooks struct {
// ResolvePhantoms re-links phantom playlist tracks whose
// files now exist in the library after scanning.
ResolvePhantoms func()
// OnAllScansComplete runs after ALL queued scans finish
// (queue drained).
OnAllScansComplete func()
}
// Library manages scanning and querying the music collection.
+5
View File
@@ -254,7 +254,12 @@ func (l *Library) drainQueue() {
l.currentScanLibraryID = 0
l.currentScanLibraryName = ""
l.scanActive = false
hooks := l.scanHooks
l.mu.Unlock()
runtime.EventsEmit(l.ctx, events.LibraryScanQueueDrained)
if hooks.OnAllScansComplete != nil {
hooks.OnAllScansComplete()
}
}