diff --git a/core/artwork/blurhash_updater.go b/core/artwork/blurhash_updater.go index cbbbe869a..d44797642 100644 --- a/core/artwork/blurhash_updater.go +++ b/core/artwork/blurhash_updater.go @@ -183,6 +183,13 @@ func (u *blurHashUpdater) process(ctx context.Context, artID model.ArtworkID, re // with a TTL: browsing stays cheap, and failures still retry once it expires. log.Trace(ctx, "BlurHash: nothing to persist", "artID", artID, err) u.setNoResult(artID, sig) + // Reaching compute with a stored hash means there was change evidence — clear it, so the + // DTO falls back to the rotating fake instead of describing artwork no longer served. + if stored != "" { + if err := u.persist(ctx, artID, "", sig); err != nil { + log.Warn(ctx, "BlurHash: error clearing stale hash", "artID", artID, err) + } + } return } if err := u.persist(ctx, artID, hash, sig); err != nil { diff --git a/core/artwork/blurhash_updater_internal_test.go b/core/artwork/blurhash_updater_internal_test.go index 8120649d5..1807ca0d1 100644 --- a/core/artwork/blurhash_updater_internal_test.go +++ b/core/artwork/blurhash_updater_internal_test.go @@ -101,6 +101,21 @@ var _ = Describe("blurHashUpdater", func() { Expect(last.sig).To(Equal(newer)) }) + It("clears a stored hash when a recompute with change evidence yields no result", func() { + storedAt := version.Add(-time.Hour) + al := model.Album{ID: "al-1", UpdatedAt: version, BlurHash: "LEHV6nWB2yk8", BlurHashUpdatedAt: &storedAt} + repo := tests.CreateMockAlbumRepo() + repo.SetData(model.Albums{al}) + ds.MockedAlbum = repo + ds.MockedFolder = failingFolderRepo{} + + // storedAt < version = change evidence; the compute fails, so the stale hash must go. + u.process(GinkgoT().Context(), al.CoverArtID(), enqueueRequest{imageUpdatedAt: version}) + stored, err := ds.Album(GinkgoT().Context()).Get("al-1") + Expect(err).ToNot(HaveOccurred()) + Expect(stored.BlurHash).To(BeEmpty()) + }) + It("does nothing when the entity is gone", func() { ds.MockedAlbum = tests.CreateMockAlbumRepo() Expect(func() {