mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
fix(lastfm): return agents.ErrNotFound on error 6 (not found)
Last.fm returns error 6 for a missing artist/album — a definitive negative — but the agent returned the raw *lastFMError, so the artwork worker treated every not-found as a real fault: it counted toward the per-source circuit breaker (5 in a row opens it, fast-failing all Last.fm calls including valid ones) and was retried as a transient error instead of settling absent. On a first scan of a library with many artists Last.fm lacks, this stalled valid cover lookups and left entities churning in backoff. Translate error 6 to the shared agents.ErrNotFound at the agent boundary (callAlbumGetInfo / callArtistGetInfo), matching how the Deezer agent maps its client's not-found, and log it at Debug instead of Error — which also removes the not-found log spam.
This commit is contained in:
parent
8b844ee102
commit
45509d0155
@ -291,12 +291,13 @@ func (l *lastfmAgent) GetArtistImages(ctx context.Context, _, name, mbid string)
|
||||
func (l *lastfmAgent) callAlbumGetInfo(ctx context.Context, name, artist, lang string) (*Album, error) {
|
||||
a, err := l.client.albumGetInfo(ctx, name, artist, "", lang)
|
||||
if err != nil {
|
||||
var lfErr *lastFMError
|
||||
if errors.As(err, &lfErr) && lfErr.Code == 6 {
|
||||
log.Debug(ctx, "Album not found", "album", name, "artist", artist, err)
|
||||
} else {
|
||||
log.Error(ctx, "Error calling LastFM/album.getInfo", "album", name, "artist", artist, err)
|
||||
if lfErr, ok := errors.AsType[*lastFMError](err); ok && lfErr.Code == 6 {
|
||||
// A not-found is a definitive absence, not a fault: return the shared sentinel so the
|
||||
// artwork worker's breaker/transient checks don't retry it, and log it at Debug.
|
||||
log.Debug(ctx, "Album not found in Last.fm", "album", name, "artist", artist)
|
||||
return nil, agents.ErrNotFound
|
||||
}
|
||||
log.Error(ctx, "Error calling LastFM/album.getInfo", "album", name, "artist", artist, err)
|
||||
return nil, err
|
||||
}
|
||||
return a, nil
|
||||
@ -308,6 +309,12 @@ func (l *lastfmAgent) callArtistGetInfo(ctx context.Context, name string, lang s
|
||||
|
||||
a, err := l.client.artistGetInfo(ctx, name, lang)
|
||||
if err != nil {
|
||||
if lfErr, ok := errors.AsType[*lastFMError](err); ok && lfErr.Code == 6 {
|
||||
// A not-found is a definitive absence, not a fault: return the shared sentinel so it
|
||||
// doesn't trip the artwork worker's breaker, and log at Debug instead of Error.
|
||||
log.Debug(ctx, "Artist not found in Last.fm", "artist", name)
|
||||
return nil, agents.ErrNotFound
|
||||
}
|
||||
log.Error(ctx, "Error calling LastFM/artist.getInfo", "artist", name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -576,6 +576,9 @@ var _ = Describe("lastfmAgent", func() {
|
||||
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
|
||||
_, err := agent.GetAlbumInfo(ctx, "123", "U2", "mbid-1234")
|
||||
Expect(err).To(HaveOccurred())
|
||||
// error 6 is a definitive not-found, so it must satisfy the shared sentinel (else it
|
||||
// would trip the artwork worker's circuit breaker and be retried as a transient fault).
|
||||
Expect(errors.Is(err, agents.ErrNotFound)).To(BeTrue())
|
||||
// No MBID retry: album.getInfo is queried by name+artist only, in a single call.
|
||||
Expect(httpClient.RequestCount).To(Equal(1))
|
||||
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
|
||||
@ -609,6 +612,14 @@ var _ = Describe("lastfmAgent", func() {
|
||||
Expect(images[0].URL).To(Equal("https://lastfm.freetls.fastly.net/i/u/ar0/818148bf682d429dc21b59a73ef6f68e.png"))
|
||||
})
|
||||
|
||||
It("maps a Last.fm error 6 (artist not found) to the shared not-found sentinel", func() {
|
||||
apiClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
|
||||
_, err := agent.GetArtistImages(ctx, "123", "Nonexistent Artist", "")
|
||||
// Definitive not-found, not a fault — must satisfy the sentinel through the %w wrap so
|
||||
// runs of missing artists never trip the artwork worker's circuit breaker.
|
||||
Expect(errors.Is(err, agents.ErrNotFound)).To(BeTrue())
|
||||
})
|
||||
|
||||
It("returns empty list if image is the ignored default image", func() {
|
||||
fApi, _ := os.Open("tests/fixtures/lastfm.artist.getinfo.json")
|
||||
apiClient.Res = http.Response{Body: fApi, StatusCode: 200}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user