diff --git a/model/share.go b/model/share.go index ce0846d60..9f49b6a2a 100644 --- a/model/share.go +++ b/model/share.go @@ -37,6 +37,10 @@ func (s Share) CoverArtID() ArtworkID { } switch s.ResourceType { case "album": + // Use the loaded album when available, so the public URL busts on cover edits + if len(s.Albums) > 0 && s.Albums[0].ID == ids[0] { + return s.Albums[0].CoverArtID() + } return Album{ID: ids[0]}.CoverArtID() case "playlist": return Playlist{ID: ids[0]}.CoverArtID() diff --git a/model/share_test.go b/model/share_test.go new file mode 100644 index 000000000..1304b960d --- /dev/null +++ b/model/share_test.go @@ -0,0 +1,24 @@ +package model_test + +import ( + "time" + + . "github.com/navidrome/navidrome/model" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Share", func() { + Describe("CoverArtID", func() { + It("uses the loaded album, so the public URL busts on cover edits", func() { + s := Share{ResourceType: "album", ResourceIDs: "al-1"} + plain := s.CoverArtID().String() + Expect(s.CoverArtID().ID).To(Equal("al-1")) + + stamp := time.Now() + s.Albums = Albums{{ID: "al-1", CoverArtUpdatedAt: &stamp}} + Expect(s.CoverArtID().ID).To(Equal("al-1")) + Expect(s.CoverArtID().String()).ToNot(Equal(plain)) + }) + }) +}) diff --git a/persistence/album_repository.go b/persistence/album_repository.go index 3add4e90e..6323669ce 100644 --- a/persistence/album_repository.go +++ b/persistence/album_repository.go @@ -299,10 +299,14 @@ func (r *albumRepository) CopyAttributes(fromID, toID string, columns ...string) if col == "created_at" && (!v.Valid || v.String == "" || strings.HasPrefix(v.String, "0001-")) { continue } - // A source without an uploaded cover must not wipe one the destination has. + // A source without an uploaded cover must not wipe one the destination has, + // nor contribute a stale cover stamp left behind by a cover removal. if (col == "uploaded_image" || col == "cover_art_updated_at") && (!v.Valid || v.String == "") { continue } + if col == "cover_art_updated_at" && (!from["uploaded_image"].Valid || from["uploaded_image"].String == "") { + continue + } to[col] = v } if len(to) == 0 { diff --git a/persistence/album_repository_test.go b/persistence/album_repository_test.go index a08425f85..c18ca2e08 100644 --- a/persistence/album_repository_test.go +++ b/persistence/album_repository_test.go @@ -174,6 +174,20 @@ var _ = Describe("AlbumRepository", func() { Expect(got.UploadedImage).To(Equal("copy-dst_cover.jpg")) Expect(got.CoverArtUpdatedAt).ToNot(BeNil()) }) + It("does not copy a stale cover stamp left behind by a cover removal", func() { + // A removal clears uploaded_image but keeps cover_art_updated_at set + Expect(albumRepo.UpdateImage("copy-src", "copy-src_cover.jpg")).To(Succeed()) + Expect(albumRepo.UpdateImage("copy-src", "")).To(Succeed()) + Expect(albumRepo.UpdateImage("copy-dst", "copy-dst_cover.jpg")).To(Succeed()) + before, err := albumRepo.Get("copy-dst") + Expect(err).ToNot(HaveOccurred()) + + Expect(albumRepo.CopyAttributes("copy-src", "copy-dst", "uploaded_image", "cover_art_updated_at")).To(Succeed()) + got, err := albumRepo.Get("copy-dst") + Expect(err).ToNot(HaveOccurred()) + Expect(got.UploadedImage).To(Equal("copy-dst_cover.jpg")) + Expect(got.CoverArtUpdatedAt.Equal(*before.CoverArtUpdatedAt)).To(BeTrue()) + }) }) Describe("GetCursor", func() {