- Honor ignore_singles/ignore_compilations at scanner read time so toggles take effect immediately on the dashboard, artist page, and digest instead of waiting for the MusicBrainz cache to expire and prune rows. - Run notifier notify synchronously in the scheduler loop to avoid overlapping read-send-mark runs double-sending the digest. - Show artist name (with ID fallback) on the archive page instead of raw IDs. - Select last_synced in GetAllArtistSettings for contract consistency. - Fix stale startPeriodicSync comment and remove redundant error var. - Remove dead ignored-branch from the artist template (never rendered). - Add tests: CSRF sameOrigin, ArtistCacheFresh, secondary_types round-trip, and scanner type-toggle filtering. - Update Specification.md schema/config to reflect mbid, last_synced, secondary_types, sync.interval, and server.public_url.
87 lines
2.7 KiB
Go
87 lines
2.7 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 {
|
|
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
|
|
}
|