Creates internal/musicbrainz/sync.go with SyncArtistDiscography function that orchestrates the full flow: cache check via GetCachedReleases, fetch from MusicBrainz API on cache miss, filter via FilterReleaseGroups, and upsert into external_releases via database.SaveExternalRelease. Includes SyncArtistDiscographyWithFilter variant for per-artist type filtering. All functions support context cancellation. 14 new tests cover cache hit/miss, status/type filtering, context cancellation, idempotency, API errors, and full XML pipeline integration.
131 lines
3.9 KiB
Go
131 lines
3.9 KiB
Go
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
|
|
}
|