From 4a6db5a12383afbb0664bf1af254a80c44bacaf1 Mon Sep 17 00:00:00 2001 From: sproutsberry <238929279+sproutsberry@users.noreply.github.com> Date: Thu, 12 Feb 2026 20:03:02 -0500 Subject: [PATCH] Implement 5 second timeout for Cover Art Archive --- coverart.go | 29 ++++++++++++++++++++--------- coverart_test.go | 15 +++++++++++++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/coverart.go b/coverart.go index af62f62..dbc3f65 100644 --- a/coverart.go +++ b/coverart.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "github.com/navidrome/navidrome/plugins/pdk/go/host" "github.com/navidrome/navidrome/plugins/pdk/go/pdk" @@ -110,6 +111,8 @@ func uploadToUguu(imageData []byte, contentType string) (string, error) { // Cover Art Archive // ============================================================================ +const CAA_TIMEOUT = 5 * time.Second + // caaResponse only includes relevant parameters; see API for full response // https://musicbrainz.org/doc/Cover_Art_Archive/API type caaResponse struct { @@ -127,18 +130,26 @@ type caaResponse struct { func getThumbnailForMBZAlbumID(mbzAlbumID string) (string, error) { req := pdk.NewHTTPRequest(pdk.MethodGet, fmt.Sprintf("https://coverartarchive.org/release/%s", mbzAlbumID)) - resp := req.Send() - if status := resp.Status(); status == 404 { - pdk.Log(pdk.LogDebug, fmt.Sprintf("No cover art for MusicBrainz Album ID: %s", mbzAlbumID)) - return "", nil - } else if status >= 400 { - return "", fmt.Errorf("HTTP %d", resp.Status()) - } + respChan := make(chan pdk.HTTPResponse, 1) + go func() { respChan <- req.Send() }() var result caaResponse - if err := json.Unmarshal(resp.Body(), &result); err != nil { - return "", fmt.Errorf("failed to parse: %w", err) + + select { + case resp := <-respChan: + if status := resp.Status(); status == 404 { + pdk.Log(pdk.LogDebug, fmt.Sprintf("No cover art for MusicBrainz Album ID: %s", mbzAlbumID)) + return "", nil + } else if status >= 400 { + return "", fmt.Errorf("HTTP %d", resp.Status()) + } + + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return "", fmt.Errorf("failed to parse: %w", err) + } + case <-time.After(CAA_TIMEOUT): + return "", fmt.Errorf("Timed out") } for _, image := range result.Images { diff --git a/coverart_test.go b/coverart_test.go index d962556..7ffd242 100644 --- a/coverart_test.go +++ b/coverart_test.go @@ -2,6 +2,7 @@ package main import ( "errors" + "time" "github.com/navidrome/navidrome/plugins/pdk/go/host" "github.com/navidrome/navidrome/plugins/pdk/go/pdk" @@ -165,5 +166,19 @@ var _ = Describe("getImageURL", func() { Expect(url).To(Equal("https://example.com/art.jpg")) host.CacheMock.AssertCalled(GinkgoT(), "SetString", "caa.artwork.test", "", int64(86400)) }) + + It("returns artwork directly after 5 second timeout", func() { + host.CacheMock.On("GetString", "caa.artwork.test").Return("", false, nil) + host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("https://example.com/art.jpg", nil) + + // Mock coverartarchive.org HTTP get + caaReq := &pdk.HTTPRequest{} + pdk.PDKMock.On("NewHTTPRequest", pdk.MethodGet, "https://coverartarchive.org/release/test").Return(caaReq) + pdk.PDKMock.On("Send", caaReq).WaitUntil(time.After(7 * time.Second)).Return(pdk.NewStubHTTPResponse(200, nil, + []byte(`{"images":[{"front":false,"thumbnails":{"250":"https://coverartarchive.org/release/test/0-250.jpg"}}]}`))) + + url := getImageURL("testuser", scrobbler.TrackInfo{ID: "track1", MBZAlbumID: "test"}) + Expect(url).To(Equal("https://example.com/art.jpg")) + }) }) })