feat(scanner): add library stats to DB (#4229)

* Combine library stats migrations

* test: verify full library stats

* Fix total_songs calculation

* Fix library stats migration

* fix(scanner): log elapsed time and number of libraries updated during scan

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

* fix(scanner): refresh library stats conditionally, only if changes were detected

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

* fix(scanner): refresh library stats conditionally, only if changes were detected

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

* fix(scanner): update queries to exclude missing entries in library stats

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

---------

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan Quintão
2025-06-14 15:58:33 -04:00
committed by GitHub
parent 44834204de
commit 5667f6ab75
7 changed files with 180 additions and 15 deletions
+6 -13
View File
@@ -7,7 +7,6 @@ import (
"sync/atomic"
"time"
"github.com/Masterminds/squirrel"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core"
@@ -178,20 +177,14 @@ func (s *controller) Status(ctx context.Context) (*StatusInfo, error) {
}
func (s *controller) getCounters(ctx context.Context) (int64, int64, error) {
count, err := s.ds.MediaFile(ctx).CountAll()
libs, err := s.ds.Library(ctx).GetAll()
if err != nil {
return 0, 0, fmt.Errorf("media file count: %w", err)
return 0, 0, fmt.Errorf("library count: %w", err)
}
folderCount, err := s.ds.Folder(ctx).CountAll(
model.QueryOptions{
Filters: squirrel.And{
squirrel.Gt{"num_audio_files": 0},
squirrel.Eq{"missing": false},
},
},
)
if err != nil {
return 0, 0, fmt.Errorf("folder count: %w", err)
var count, folderCount int64
for _, l := range libs {
count += int64(l.TotalSongs)
folderCount += int64(l.TotalFolders)
}
return count, folderCount, nil
}
+13 -2
View File
@@ -100,7 +100,7 @@ func (s *scannerImpl) scanAll(ctx context.Context, fullScan bool, progress chan<
s.runRefreshStats(ctx, &state),
// Update last_scan_completed_at for all libraries
s.runUpdateLibraries(ctx, libs),
s.runUpdateLibraries(ctx, libs, &state),
// Optimize DB
s.runOptimize(ctx),
@@ -175,8 +175,9 @@ func (s *scannerImpl) runOptimize(ctx context.Context) func() error {
}
}
func (s *scannerImpl) runUpdateLibraries(ctx context.Context, libs model.Libraries) func() error {
func (s *scannerImpl) runUpdateLibraries(ctx context.Context, libs model.Libraries, state *scanState) func() error {
return func() error {
start := time.Now()
return s.ds.WithTx(func(tx model.DataStore) error {
for _, lib := range libs {
err := tx.Library(ctx).ScanEnd(lib.ID)
@@ -194,7 +195,17 @@ func (s *scannerImpl) runUpdateLibraries(ctx context.Context, libs model.Librari
log.Error(ctx, "Scanner: Error updating album PID conf", err)
return fmt.Errorf("updating album PID conf: %w", err)
}
if state.changesDetected.Load() {
log.Debug(ctx, "Scanner: Refreshing library stats", "lib", lib.Name)
if err := tx.Library(ctx).RefreshStats(lib.ID); err != nil {
log.Error(ctx, "Scanner: Error refreshing library stats", "lib", lib.Name, err)
return fmt.Errorf("refreshing library stats: %w", err)
}
} else {
log.Debug(ctx, "Scanner: No changes detected, skipping library stats refresh", "lib", lib.Name)
}
}
log.Debug(ctx, "Scanner: Updated libraries after scan", "elapsed", time.Since(start), "numLibraries", len(libs))
return nil
}, "scanner: update libraries")
}