diff --git a/scanner/phase_1_folders.go b/scanner/phase_1_folders.go index 3d04d525d..fb78e81a4 100644 --- a/scanner/phase_1_folders.go +++ b/scanner/phase_1_folders.go @@ -27,31 +27,14 @@ import ( ) func createPhaseFolders(ctx context.Context, state *scanState, ds model.DataStore, cw artwork.CacheWarmer) *phaseFolders { + // At this point, all libraries in state.libraries have been initialized + // (LastScanStartedAt has been set by prepareLibrariesForScan in scanner.go) var jobs []*scanJob - var updatedLibs []model.Library - for _, lib := range state.libraries { - if lib.LastScanStartedAt.IsZero() { - err := ds.Library(ctx).ScanBegin(lib.ID, state.fullScan) - if err != nil { - log.Error(ctx, "Scanner: Error updating last scan started at", "lib", lib.Name, err) - state.sendWarning(err.Error()) - continue - } - // Reload library to get updated state - l, err := ds.Library(ctx).Get(lib.ID) - if err != nil { - log.Error(ctx, "Scanner: Error reloading library", "lib", lib.Name, err) - state.sendWarning(err.Error()) - continue - } - lib = *l - } else { - log.Debug(ctx, "Scanner: Resuming previous scan", "lib", lib.Name, "lastScanStartedAt", lib.LastScanStartedAt, "fullScan", lib.FullScanInProgress) - } + for _, lib := range state.libraries { // Get target folders for this library if selective scan var targetFolders []string - if state.targets != nil { + if state.isSelectiveScan() { targetFolders = state.targets[lib.ID] } @@ -62,12 +45,8 @@ func createPhaseFolders(ctx context.Context, state *scanState, ds model.DataStor continue } jobs = append(jobs, job) - updatedLibs = append(updatedLibs, lib) } - // Update the state with the libraries that have been processed and have their scan timestamps set - state.libraries = updatedLibs - return &phaseFolders{jobs: jobs, ctx: ctx, ds: ds, state: state} } diff --git a/scanner/phase_2_missing_tracks.go b/scanner/phase_2_missing_tracks.go index a6c0e261e..de93ed6ee 100644 --- a/scanner/phase_2_missing_tracks.go +++ b/scanner/phase_2_missing_tracks.go @@ -69,9 +69,6 @@ func (p *phaseMissingTracks) produce(put func(tracks *missingTracks)) error { } } for _, lib := range p.state.libraries { - if lib.LastScanStartedAt.IsZero() { - continue - } log.Debug(p.ctx, "Scanner: Checking missing tracks", "libraryId", lib.ID, "libraryName", lib.Name) cursor, err := p.ds.MediaFile(p.ctx).GetMissingAndMatching(lib.ID) if err != nil { diff --git a/scanner/scanner.go b/scanner/scanner.go index c0bf98c26..539e52933 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -123,6 +123,14 @@ func (s *scannerImpl) scanFolders(ctx context.Context, fullScan bool, targets [] } } + // Prepare libraries for scanning (initialize LastScanStartedAt if needed) + err = s.prepareLibrariesForScan(ctx, &state) + if err != nil { + log.Error(ctx, "Scanner: Error preparing libraries for scan", err) + state.sendError(err) + return + } + err = run.Sequentially( // Phase 1: Scan all libraries and import new/updated files runPhase[*folderEntry](ctx, 1, createPhaseFolders(ctx, &state, s.ds, s.cw)), @@ -173,6 +181,48 @@ func (s *scannerImpl) scanFolders(ctx context.Context, fullScan bool, targets [] } } +// prepareLibrariesForScan initializes the scan for all libraries in the state. +// It calls ScanBegin for libraries that haven't started scanning yet (LastScanStartedAt is zero), +// reloads them to get the updated state, and filters out any libraries that fail to initialize. +func (s *scannerImpl) prepareLibrariesForScan(ctx context.Context, state *scanState) error { + var successfulLibs []model.Library + + for _, lib := range state.libraries { + if lib.LastScanStartedAt.IsZero() { + // This is a new scan - mark it as started + err := s.ds.Library(ctx).ScanBegin(lib.ID, state.fullScan) + if err != nil { + log.Error(ctx, "Scanner: Error marking scan start", "lib", lib.Name, err) + state.sendWarning(err.Error()) + continue + } + + // Reload library to get updated state (timestamps, etc.) + reloadedLib, err := s.ds.Library(ctx).Get(lib.ID) + if err != nil { + log.Error(ctx, "Scanner: Error reloading library", "lib", lib.Name, err) + state.sendWarning(err.Error()) + continue + } + lib = *reloadedLib + } else { + // This is a resumed scan + log.Debug(ctx, "Scanner: Resuming previous scan", "lib", lib.Name, + "lastScanStartedAt", lib.LastScanStartedAt, "fullScan", lib.FullScanInProgress) + } + + successfulLibs = append(successfulLibs, lib) + } + + if len(successfulLibs) == 0 { + return fmt.Errorf("no libraries available for scanning") + } + + // Update state with only successfully initialized libraries + state.libraries = successfulLibs + return nil +} + func (s *scannerImpl) runGC(ctx context.Context, state *scanState) func() error { return func() error { state.sendProgress(&ProgressInfo{ForceUpdate: true})