diff --git a/cmd/root.go b/cmd/root.go index b3acd9fdc..6954c1925 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -68,9 +68,9 @@ func startServer() (func() error, func(err error)) { return a.Run(fmt.Sprintf("%s:%d", conf.Server.Address, conf.Server.Port)) }, func(err error) { if err != nil { - log.Error("Fatal error executing Scanner", err) + log.Error("Shutting down Server due to error", err) } else { - log.Info("Shutting down Scanner") + log.Info("Shutting down Server") } } } @@ -79,19 +79,23 @@ func startScanner() (func() error, func(err error)) { interval := conf.Server.ScanInterval log.Info("Starting scanner", "interval", interval.String()) scanner := GetScanner() + done := make(chan struct{}) return func() error { if interval != 0 { - go func() { - time.Sleep(2 * time.Second) // Wait 2 seconds before the first scan - scanner.RescanAll(false) - }() + time.Sleep(2 * time.Second) // Wait 2 seconds before the first scan + scanner.Start(interval) + } else { + log.Warn("Periodic scan is DISABLED", "interval", interval) } - return scanner.Start(interval) + + <-done + return nil }, func(err error) { scanner.Stop() + done <- struct{}{} if err != nil { - log.Error("Fatal error executing Scanner", err) + log.Error("Shutting down Scanner due to error", err) } else { log.Info("Shutting down Scanner") } diff --git a/cmd/scan.go b/cmd/scan.go index 93fc092b9..49d8bb10a 100644 --- a/cmd/scan.go +++ b/cmd/scan.go @@ -1,10 +1,8 @@ package cmd import ( - "time" - + "github.com/deluan/navidrome/conf" "github.com/deluan/navidrome/log" - "github.com/deluan/navidrome/scanner" "github.com/spf13/cobra" ) @@ -24,23 +22,11 @@ var scanCmd = &cobra.Command{ }, } -func waitScanToFinish(scanner scanner.Scanner) { - time.Sleep(500 * time.Millisecond) - ticker := time.Tick(100 * time.Millisecond) - for { - if !scanner.Scanning() { - return - } - <-ticker - } -} - func runScanner() { + conf.Server.DevPreCacheAlbumArtwork = false + scanner := GetScanner() - go func() { _ = scanner.Start(0) }() - scanner.RescanAll(fullRescan) - waitScanToFinish(scanner) - scanner.Stop() + _ = scanner.RescanAll(fullRescan) if fullRescan { log.Info("Finished full rescan") } else { diff --git a/scanner/scanner.go b/scanner/scanner.go index 28bc80605..2fae98aac 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -12,12 +12,13 @@ import ( "github.com/deluan/navidrome/core" "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" + "github.com/deluan/navidrome/utils" ) type Scanner interface { - Start(interval time.Duration) error + Start(interval time.Duration) Stop() - RescanAll(fullRescan bool) + RescanAll(fullRescan bool) error Status(mediaFolder string) (*StatusInfo, error) Scanning() bool } @@ -29,10 +30,17 @@ type StatusInfo struct { Count uint32 } +var ( + ErrAlreadyScanning = errors.New("already scanning") + ErrScanError = errors.New("scan error") +) + type FolderScanner interface { Scan(ctx context.Context, lastModifiedSince time.Time, progress chan uint32) error } +var isScanning utils.AtomicBool + type scanner struct { folders map[string]FolderScanner status map[string]*scanStatus @@ -63,25 +71,19 @@ func New(ds model.DataStore, cacheWarmer core.CacheWarmer) Scanner { return s } -func (s *scanner) Start(interval time.Duration) error { - var ticker *time.Ticker - if interval == 0 { - log.Warn("Periodic scan is DISABLED", "interval", interval) - ticker = time.NewTicker(1 * time.Hour) - } else { - ticker = time.NewTicker(interval) - } +func (s *scanner) Start(interval time.Duration) { + ticker := time.NewTicker(interval) defer ticker.Stop() for { + err := s.RescanAll(false) + if err != nil { + log.Error(err) + } select { - case full := <-s.scan: - s.rescanAll(full) case <-ticker.C: - if interval != 0 { - s.rescanAll(false) - } + continue case <-s.done: - return nil + return } } } @@ -94,6 +96,9 @@ func (s *scanner) rescan(mediaFolder string, fullRescan bool) error { folderScanner := s.folders[mediaFolder] start := time.Now() + s.setStatusStart(mediaFolder) + defer s.setStatusEnd(mediaFolder, start) + lastModifiedSince := time.Time{} if !fullRescan { lastModifiedSince = s.getLastModifiedSince(mediaFolder) @@ -102,10 +107,7 @@ func (s *scanner) rescan(mediaFolder string, fullRescan bool) error { log.Debug("Scanning folder (full scan)", "folder", mediaFolder) } - s.setStatusActive(mediaFolder, true) - defer s.setStatus(mediaFolder, false, 0, start) - - progress := make(chan uint32, 10) + progress := make(chan uint32) go func() { for { count, more := <-progress @@ -126,15 +128,14 @@ func (s *scanner) rescan(mediaFolder string, fullRescan bool) error { return err } -func (s *scanner) RescanAll(fullRescan bool) { +func (s *scanner) RescanAll(fullRescan bool) error { if s.Scanning() { log.Debug("Scanner already running, ignoring request for rescan.") - return + return ErrAlreadyScanning } - s.scan <- fullRescan -} + isScanning.Set(true) + defer func() { isScanning.Set(false) }() -func (s *scanner) rescanAll(fullRescan bool) { defer s.cacheWarmer.Flush(context.Background()) var hasError bool for folder := range s.folders { @@ -143,7 +144,9 @@ func (s *scanner) rescanAll(fullRescan bool) { } if hasError { log.Error("Errors while scanning media. Please check the logs") + return ErrScanError } + return nil } func (s *scanner) getStatus(folder string) *scanStatus { @@ -155,33 +158,26 @@ func (s *scanner) getStatus(folder string) *scanStatus { return nil } -func (s *scanner) setStatus(folder string, active bool, count uint32, lastUpdate time.Time) { +func (s *scanner) setStatusStart(folder string) { s.lock.Lock() defer s.lock.Unlock() if status, ok := s.status[folder]; ok { - status.active = active - status.count = count + status.active = true + status.count = 0 + } +} + +func (s *scanner) setStatusEnd(folder string, lastUpdate time.Time) { + s.lock.Lock() + defer s.lock.Unlock() + if status, ok := s.status[folder]; ok { + status.active = false status.lastUpdate = lastUpdate } } -func (s *scanner) setStatusActive(folder string, active bool) { - s.lock.Lock() - defer s.lock.Unlock() - if status, ok := s.status[folder]; ok { - status.active = active - } -} - func (s *scanner) Scanning() bool { - s.lock.RLock() - defer s.lock.RUnlock() - for _, status := range s.status { - if status.active { - return true - } - } - return false + return isScanning.Get() } func (s *scanner) Status(mediaFolder string) (*StatusInfo, error) { diff --git a/server/subsonic/library_scanning.go b/server/subsonic/library_scanning.go index b3ea5caf8..21bdc793b 100644 --- a/server/subsonic/library_scanning.go +++ b/server/subsonic/library_scanning.go @@ -48,7 +48,13 @@ func (c *LibraryScanningController) StartScan(w http.ResponseWriter, r *http.Req } fullScan := utils.ParamBool(r, "fullScan", false) - c.scanner.RescanAll(fullScan) + + go func() { + err := c.scanner.RescanAll(fullScan) + if err != nil { + log.Error(r.Context(), err) + } + }() return c.GetScanStatus(w, r) }