feat(ui): add scan progress and error reporting to UI (#4094)

* feat(scanner): add LastScanError tracking to scanner status

- Introduced LastScanErrorKey constant for error tracking.
- Updated StatusInfo struct to include LastError field.
- Modified scanner logic to store and retrieve last scan error.
- Enhanced ScanStatus response to include error information.
- Updated UI components to display last scan error when applicable.
- Added tests to verify last scan error functionality.

Signed-off-by: Deluan <deluan@navidrome.org>

* feat(scanner): enhance scan status with type and elapsed time tracking

- Added LastScanTypeKey and LastScanStartTimeKey constants for tracking scan type and start time.
- Updated StatusInfo struct to include ScanType and ElapsedTime fields.
- Implemented getScanInfo method to retrieve scan type, elapsed time, and last error.
- Modified scanner logic to store scan type and start time during scans.
- Enhanced ScanStatus response and UI components to display scan type and elapsed time.
- Added formatShortDuration utility for better elapsed time representation.
- Updated activity reducer to handle new scan status fields.

Signed-off-by: Deluan <deluan@navidrome.org>

* refactor(tests): consolidate controller status tests into a single file

- Removed the old controller_status_test.go file.
- Merged relevant tests into the new controller_test.go file for better organization and maintainability.
- Ensured all existing test cases for controller status are preserved and functional.

Signed-off-by: Deluan <deluan@navidrome.org>

* Fix formatting issues

* refactor(scanner): update getScanInfo method documentation

---------

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan Quintão
2025-05-21 09:30:23 -04:00
committed by GitHub
parent fef1739c1a
commit 6880cffd16
13 changed files with 394 additions and 22 deletions
+47
View File
@@ -9,6 +9,7 @@ import (
"github.com/Masterminds/squirrel"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/core/artwork"
"github.com/navidrome/navidrome/core/auth"
@@ -37,6 +38,9 @@ type StatusInfo struct {
LastScan time.Time
Count uint32
FolderCount uint32
LastError string
ScanType string
ElapsedTime time.Duration
}
func New(rootCtx context.Context, ds model.DataStore, cw artwork.CacheWarmer, broker events.Broker,
@@ -113,20 +117,51 @@ type controller struct {
changesDetected bool
}
// getScanInfo retrieves scan status from the database
func (s *controller) getScanInfo(ctx context.Context) (scanType string, elapsed time.Duration, lastErr string) {
lastErr, _ = s.ds.Property(ctx).DefaultGet(consts.LastScanErrorKey, "")
scanType, _ = s.ds.Property(ctx).DefaultGet(consts.LastScanTypeKey, "")
startTimeStr, _ := s.ds.Property(ctx).DefaultGet(consts.LastScanStartTimeKey, "")
if startTimeStr != "" {
startTime, err := time.Parse(time.RFC3339, startTimeStr)
if err == nil {
if running.Load() {
elapsed = time.Since(startTime)
} else {
// If scan is not running, try to get the last scan time for the library
lib, err := s.ds.Library(ctx).Get(1) //TODO Multi-library
if err == nil {
elapsed = lib.LastScanAt.Sub(startTime)
}
}
}
}
return scanType, elapsed, lastErr
}
func (s *controller) Status(ctx context.Context) (*StatusInfo, error) {
lib, err := s.ds.Library(ctx).Get(1) //TODO Multi-library
if err != nil {
return nil, fmt.Errorf("getting library: %w", err)
}
scanType, elapsed, lastErr := s.getScanInfo(ctx)
if running.Load() {
status := &StatusInfo{
Scanning: true,
LastScan: lib.LastScanAt,
Count: s.count.Load(),
FolderCount: s.folderCount.Load(),
LastError: lastErr,
ScanType: scanType,
ElapsedTime: elapsed,
}
return status, nil
}
count, folderCount, err := s.getCounters(ctx)
if err != nil {
return nil, fmt.Errorf("getting library stats: %w", err)
@@ -136,6 +171,9 @@ func (s *controller) Status(ctx context.Context) (*StatusInfo, error) {
LastScan: lib.LastScanAt,
Count: uint32(count),
FolderCount: uint32(folderCount),
LastError: lastErr,
ScanType: scanType,
ElapsedTime: elapsed,
}, nil
}
@@ -193,10 +231,14 @@ func (s *controller) ScanAll(requestCtx context.Context, fullScan bool) ([]strin
if count, folderCount, err := s.getCounters(ctx); err != nil {
return scanWarnings, err
} else {
scanType, elapsed, lastErr := s.getScanInfo(ctx)
s.sendMessage(ctx, &events.ScanStatus{
Scanning: false,
Count: count,
FolderCount: folderCount,
Error: lastErr,
ScanType: scanType,
ElapsedTime: elapsed,
})
}
return scanWarnings, scanError
@@ -240,10 +282,15 @@ func (s *controller) trackProgress(ctx context.Context, progress <-chan *Progres
if p.FileCount > 0 {
s.folderCount.Add(1)
}
scanType, elapsed, lastErr := s.getScanInfo(ctx)
status := &events.ScanStatus{
Scanning: true,
Count: int64(s.count.Load()),
FolderCount: int64(s.folderCount.Load()),
Error: lastErr,
ScanType: scanType,
ElapsedTime: elapsed,
}
if s.limiter != nil {
s.limiter.Do(func() { s.sendMessage(ctx, status) })