diff --git a/internal/database/external_releases.go b/internal/database/external_releases.go index 62d4388..6b9b567 100644 --- a/internal/database/external_releases.go +++ b/internal/database/external_releases.go @@ -30,7 +30,7 @@ func GetExternalRelease(db *DB, rgid string) (*ExternalRelease, error) { func SaveExternalRelease(db *DB, release *ExternalRelease) error { var cachedAt interface{} if !release.CachedAt.IsZero() { - cachedAt = release.CachedAt.Format("2006-01-02 15:04:05") + cachedAt = release.CachedAt.UTC().Format("2006-01-02 15:04:05") } _, err := db.Conn().Exec( "INSERT OR REPLACE INTO external_releases (rgid, artist_id, title, type, release_date, is_ignored, cached_at) VALUES (?, ?, ?, ?, ?, ?, ?)", @@ -145,7 +145,7 @@ func CountExternalReleases(db *DB) (int, error) { // GetExternalReleasesByArtistWithCache returns cached external_release rows for a given artist_id // that are within the specified TTL. func GetExternalReleasesByArtistWithCache(db *DB, artistID string, ttl time.Duration) ([]ExternalRelease, error) { - cutoff := time.Now().Add(-ttl) + cutoff := time.Now().UTC().Add(-ttl) rows, err := db.Conn().Query( "SELECT rgid, artist_id, title, type, release_date, is_ignored, cached_at FROM external_releases WHERE artist_id = ? AND cached_at >= ?", artistID, cutoff.Format("2006-01-02 15:04:05"), diff --git a/internal/musicbrainz/api.go b/internal/musicbrainz/api.go index 7aa0327..36a8ade 100644 --- a/internal/musicbrainz/api.go +++ b/internal/musicbrainz/api.go @@ -26,6 +26,14 @@ var includedTypes = map[string]bool{ "Compilation": true, } +// Precompiled regexes for NormalizeString — compiled once at package init. +var ( + bracketRe = regexp.MustCompile(`\[[^\]]*\]`) + parenRe = regexp.MustCompile(`\([^)]*\)`) + yearRe = regexp.MustCompile(`\b(1[0-9]{3}|2[0-9]{3})\b`) + spaceRe = regexp.MustCompile(`\s+`) +) + // GetArtistReleaseGroups fetches all release groups for a given artist from MusicBrainz. // It queries the artist's release groups via the MusicBrainz Web Service API, // parses the XML response, and applies status and type filtering. @@ -57,7 +65,9 @@ func (c *MusicBrainzClient) GetArtistReleaseGroups(ctx context.Context, artistMB allGroups = append(allGroups, parsed.ReleaseGroups...) // If we've fetched all results, we've reached the end. - if offset+len(parsed.ReleaseGroups) >= parsed.Count { + // Also break on empty page to prevent infinite loop if API + // returns fewer items than advertised by count. + if len(parsed.ReleaseGroups) == 0 || offset+len(parsed.ReleaseGroups) >= parsed.Count { break } @@ -111,15 +121,12 @@ func NormalizeString(s string) string { s = strings.ToLower(s) // Remove bracketed content first (e.g., [Deluxe Edition], [Remastered 2020]) - bracketRe := regexp.MustCompile(`\[[^\]]*\]`) s = bracketRe.ReplaceAllString(s, "") // Remove parenthesized content (e.g., (Deluxe), (Remastered)) - parenRe := regexp.MustCompile(`\([^)]*\)`) s = parenRe.ReplaceAllString(s, "") // Remove years (4-digit numbers between 1000-2999) - yearRe := regexp.MustCompile(`\b(1[0-9]{3}|2[0-9]{3})\b`) s = yearRe.ReplaceAllString(s, "") // Replace common separators with spaces before stripping other special chars @@ -136,7 +143,6 @@ func NormalizeString(s string) string { s = b.String() // Collapse multiple spaces - spaceRe := regexp.MustCompile(`\s+`) s = spaceRe.ReplaceAllString(s, " ") // Trim diff --git a/internal/musicbrainz/sync.go b/internal/musicbrainz/sync.go index d9f3b85..282537b 100644 --- a/internal/musicbrainz/sync.go +++ b/internal/musicbrainz/sync.go @@ -55,7 +55,7 @@ func SyncArtistDiscography( filtered := FilterReleaseGroups(groups) // Step 5: Upsert within a transaction — delete old entries first, then insert new ones. - now := time.Now() + now := time.Now().UTC() tx, err := db.Begin() if err != nil { return nil, fmt.Errorf("sync artist discography: begin transaction: %w", err)