package musicbrainz import ( "context" "fmt" "time" "naviwatcher/internal/database" ) // SyncArtistDiscography synchronizes an artist's discography from MusicBrainz // into the local external_releases table. It follows this flow: // 1. Check if cached data exists and is within TTL. // 2. If cache hit, return the cached releases immediately. // 3. If cache miss or expired, fetch release groups from MusicBrainz API. // 4. Apply status and type filtering. // 5. Upsert each filtered release group into external_releases with current timestamp. // 6. Return the list of external releases. // // Context cancellation is checked before the API call and between each upsert // to allow graceful interruption. func SyncArtistDiscography( ctx context.Context, client *MusicBrainzClient, db *database.DB, artistMBID string, ttl time.Duration, ) ([]database.ExternalRelease, error) { // Check context before starting. if err := ctx.Err(); err != nil { return nil, fmt.Errorf("sync artist discography: %w", err) } // Step 1: Check cache. cached, err := GetCachedReleases(db, artistMBID, ttl) if err != nil { return nil, fmt.Errorf("sync artist discography: cache check failed: %w", err) } // Step 2: If we have cached data, return it. if cached.CacheHitCount > 0 { return database.GetExternalReleasesByArtistWithCache(db, artistMBID, ttl) } // Step 3: Cache miss — fetch from MusicBrainz API. groups, err := client.GetArtistReleaseGroups(ctx, artistMBID) if err != nil { return nil, fmt.Errorf("sync artist discography: fetch release groups: %w", err) } // Step 4: Apply filtering. filtered := FilterReleaseGroups(groups) // Step 5: Upsert each release group into the database. now := time.Now() var releases []database.ExternalRelease for _, rg := range filtered { // Check context cancellation between each upsert. if err := ctx.Err(); err != nil { return nil, fmt.Errorf("sync artist discography: %w", err) } ext := rg.ToExternalRelease() ext.CachedAt = now if err := database.SaveExternalRelease(db, ext); err != nil { return nil, fmt.Errorf("sync artist discography: save release %s: %w", rg.ID, err) } releases = append(releases, *ext) } return releases, nil } // SyncArtistDiscographyWithFilter works like SyncArtistDiscography but applies // per-artist type filtering preferences in addition to the base filters. func SyncArtistDiscographyWithFilter( ctx context.Context, client *MusicBrainzClient, db *database.DB, artistMBID string, ttl time.Duration, artistFilter *ArtistTypeFilter, ) ([]database.ExternalRelease, error) { // Check context before starting. if err := ctx.Err(); err != nil { return nil, fmt.Errorf("sync artist discography with filter: %w", err) } // Step 1: Check cache. cached, err := GetCachedReleases(db, artistMBID, ttl) if err != nil { return nil, fmt.Errorf("sync artist discography with filter: cache check failed: %w", err) } // Step 2: If we have cached data, return it. if cached.CacheHitCount > 0 { return database.GetExternalReleasesByArtistWithCache(db, artistMBID, ttl) } // Step 3: Cache miss — fetch from MusicBrainz API. groups, err := client.GetArtistReleaseGroups(ctx, artistMBID) if err != nil { return nil, fmt.Errorf("sync artist discography with filter: fetch release groups: %w", err) } // Step 4: Apply filtering with artist-specific type preferences. filtered := FilterReleaseGroupsWithArtistFilter(groups, artistFilter) // Step 5: Upsert each release group into the database. now := time.Now() var releases []database.ExternalRelease for _, rg := range filtered { if err := ctx.Err(); err != nil { return nil, fmt.Errorf("sync artist discography with filter: %w", err) } ext := rg.ToExternalRelease() ext.CachedAt = now if err := database.SaveExternalRelease(db, ext); err != nil { return nil, fmt.Errorf("sync artist discography with filter: save release %s: %w", rg.ID, err) } releases = append(releases, *ext) } return releases, nil }