feat: add DB-backed scanner entrypoint and compute-only main hook

Implements ScanArtist/ScanAll in internal/scanner loading local/external
releases via the database layer with ctx-cancellation checks, plus a
compute-only run() hook that logs missing-release counts. Add table-driven
tests using in-memory SQLite fixtures.
This commit is contained in:
2026-07-19 18:19:57 +03:00
parent a4f0460664
commit c2da8c2095
6 changed files with 348 additions and 8 deletions

66
internal/scanner/scan.go Normal file
View File

@@ -0,0 +1,66 @@
package scanner
import (
"context"
"naviwatcher/internal/database"
)
// ScanArtist loads the local albums and external releases for a single artist
// from the database and computes the list of missing releases.
//
// threshold is the fuzzy-similarity cutoff; pass 0 to use DefaultThreshold.
// The context is checked before querying the database; if it is already
// cancelled, no work is performed and the sentinel error ctx.Err() is returned.
func ScanArtist(ctx context.Context, db *database.DB, artistID string, threshold float64) ([]MissingRelease, error) {
if err := ctx.Err(); err != nil {
return nil, err
}
local, err := database.GetLocalAlbumsByArtist(db, artistID)
if err != nil {
return nil, err
}
external, err := database.GetExternalReleasesByArtist(db, artistID)
if err != nil {
return nil, err
}
missing := FindMissingReleases(local, external, resolveThreshold(threshold))
return missing, nil
}
// ScanAll iterates over all monitored artists (those with Monitored == true)
// and computes the missing releases for each. Results are concatenated into a
// single slice across all artists.
//
// ctx.Err() is checked between artists; if cancellation occurs mid-iteration,
// scanning stops early and the accumulated results so far are returned along
// with the cancellation error. threshold follows the same contract as
// ScanArtist (0 → DefaultThreshold).
func ScanAll(ctx context.Context, db *database.DB, threshold float64) ([]MissingRelease, error) {
settings, err := database.GetAllArtistSettings(db)
if err != nil {
return nil, err
}
resolved := resolveThreshold(threshold)
var all []MissingRelease
for _, s := range settings {
if err := ctx.Err(); err != nil {
return all, err
}
if !s.Monitored {
continue
}
missing, err := ScanArtist(ctx, db, s.ID, resolved)
if err != nil {
return all, err
}
all = append(all, missing...)
}
return all, nil
}