Files
NaviWatcher/internal/scanner/scan.go

96 lines
3.0 KiB
Go

package scanner
import (
"context"
"log"
"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
}
// Apply the artist's type toggles at read time so ignore_singles /
// ignore_compilations changes take effect immediately, without waiting for
// the MusicBrainz cache to expire and prune rows on the next re-sync.
settings, err := database.GetArtistSettings(db, artistID)
if err != nil {
// If artist settings don't exist, use empty filter (no filtering)
if err == database.ErrArtistNotFound {
filter := TypeFilter{
IgnoreSingles: false,
IgnoreCompilations: false,
}
missing := FindMissingReleases(local, external, threshold, filter)
return missing, nil
}
return nil, err
}
filter := TypeFilter{
IgnoreSingles: settings.IgnoreSingles,
IgnoreCompilations: settings.IgnoreCompilations,
}
missing := FindMissingReleases(local, external, threshold, filter)
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
}
var all []MissingRelease
var failedArtists []string
for _, s := range settings {
if err := ctx.Err(); err != nil {
return all, err
}
if !s.Monitored {
continue
}
// A transient error for one artist must not abort the whole scan and
// take down the daemon; log it and continue with the remaining artists.
missing, err := ScanArtist(ctx, db, s.ID, threshold)
if err != nil {
log.Printf("scan artist %s failed: %v", s.ID, err)
failedArtists = append(failedArtists, s.ID)
continue
}
all = append(all, missing...)
}
if n := len(failedArtists); n > 0 {
log.Printf("scan completed with %d artist(s) skipped due to errors: %v", n, failedArtists)
}
return all, nil
}