diff --git a/core/artwork/artwork.go b/core/artwork/artwork.go index cd0d958e4..826e3ab19 100644 --- a/core/artwork/artwork.go +++ b/core/artwork/artwork.go @@ -80,6 +80,11 @@ func (a *artwork) Get(ctx context.Context, artID model.ArtworkID, size int, squa if !errors.Is(err, context.Canceled) && !errors.Is(err, ErrUnavailable) { log.Error(ctx, "Error accessing image cache", "id", artID, "size", size, err) } + // A vanished source must still reach the worker, or a stored hash would keep describing + // artwork that no longer exists. + if a.blurHashes != nil && errors.Is(err, ErrUnavailable) { + a.blurHashes.Enqueue(artID, artReader.LastUpdated(), false) + } return nil, time.Time{}, err } if a.blurHashes != nil { diff --git a/core/artwork/e2e/blurhash_test.go b/core/artwork/e2e/blurhash_test.go index 0ea75d509..90b759e6b 100644 --- a/core/artwork/e2e/blurhash_test.go +++ b/core/artwork/e2e/blurhash_test.go @@ -35,6 +35,34 @@ var _ = Describe("BlurHash", func() { }, "10s", "100ms").Should(Succeed()) }) + It("clears the stored blurhash when the cover disappears", func() { + setLayout(fstest.MapFS{ + "Artist/Album/01 - Song.mp3": trackFile(1, "Song"), + "Artist/Album/cover.png": realPNG("vanishing-cover"), + }) + scan() + al := firstAlbum() + readArtwork(al.CoverArtID()) + Eventually(func(g Gomega) { + updated, err := ds.Album(ctx).Get(al.ID) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(updated.BlurHash).ToNot(BeEmpty()) + }, "10s", "100ms").Should(Succeed()) + + setLayout(fstest.MapFS{ + "Artist/Album/01 - Song.mp3": trackFile(1, "Song"), + }) + scan() + _, err := readArtworkOrErr(al.CoverArtID()) + Expect(err).To(HaveOccurred()) + + Eventually(func(g Gomega) { + updated, err := ds.Album(ctx).Get(al.ID) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(updated.BlurHash).To(BeEmpty()) + }, "10s", "100ms").Should(Succeed()) + }) + It("does not persist a blurhash when the served image cannot be decoded", func() { setLayout(fstest.MapFS{ "Artist/Album/01 - Song.mp3": trackFile(1, "Song"),