From 9ac2c6a5e3bf56f98533f2aa0a99c8f1de1141e0 Mon Sep 17 00:00:00 2001 From: Deluan Date: Fri, 17 Jul 2026 12:57:33 -0400 Subject: [PATCH] fix(model): exclude blur hash fields from full-row writes Scanner and maintenance paths build fresh entities with empty blur hash fields, so every ordinary refresh erased the computed hash and caused a double Finamp cover refetch (fake, then real again). The fields are now read-only projections (structs:"-"): only UpdateBlurHash writes them. --- model/album.go | 7 ++++--- model/artist.go | 4 ++-- model/playlist.go | 4 ++-- persistence/album_repository_test.go | 8 ++++++++ 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/model/album.go b/model/album.go index 4e8dfae51..f4d2fdedb 100644 --- a/model/album.go +++ b/model/album.go @@ -68,9 +68,10 @@ type Album struct { CreatedAt time.Time `structs:"created_at" json:"createdAt"` // Oldest CreatedAt for all songs in this album UpdatedAt time.Time `structs:"updated_at" json:"updatedAt"` // Newest UpdatedAt for all songs in this album - // BlurHash of the album cover, computed asynchronously from the served artwork. - BlurHash string `structs:"blur_hash" json:"blurHash,omitempty" hash:"ignore"` - BlurHashUpdatedAt *time.Time `structs:"blur_hash_updated_at" json:"-" hash:"ignore"` + // BlurHash of the album cover, computed asynchronously from the served artwork. Excluded from + // full-row writes (structs:"-"): only UpdateBlurHash writes it, so scans can't erase it. + BlurHash string `structs:"-" json:"blurHash,omitempty" hash:"ignore"` + BlurHashUpdatedAt *time.Time `structs:"-" json:"-" hash:"ignore"` } func (a Album) CoverArtID() ArtworkID { diff --git a/model/artist.go b/model/artist.go index de7cd53be..12b1dc629 100644 --- a/model/artist.go +++ b/model/artist.go @@ -42,8 +42,8 @@ type Artist struct { CreatedAt *time.Time `structs:"created_at" json:"createdAt,omitempty"` UpdatedAt *time.Time `structs:"updated_at" json:"updatedAt,omitempty"` - BlurHash string `structs:"blur_hash" json:"blurHash,omitempty" hash:"ignore"` - BlurHashUpdatedAt *time.Time `structs:"blur_hash_updated_at" json:"-" hash:"ignore"` + BlurHash string `structs:"-" json:"blurHash,omitempty" hash:"ignore"` + BlurHashUpdatedAt *time.Time `structs:"-" json:"-" hash:"ignore"` } type ArtistStats struct { diff --git a/model/playlist.go b/model/playlist.go index 8970e27e2..bb12d7fe4 100644 --- a/model/playlist.go +++ b/model/playlist.go @@ -31,8 +31,8 @@ type Playlist struct { CreatedAt time.Time `structs:"created_at" json:"createdAt"` UpdatedAt time.Time `structs:"updated_at" json:"updatedAt"` - BlurHash string `structs:"blur_hash" json:"blurHash,omitempty" hash:"ignore"` - BlurHashUpdatedAt *time.Time `structs:"blur_hash_updated_at" json:"-" hash:"ignore"` + BlurHash string `structs:"-" json:"blurHash,omitempty" hash:"ignore"` + BlurHashUpdatedAt *time.Time `structs:"-" json:"-" hash:"ignore"` // SmartPlaylist attributes Rules *criteria.Criteria `structs:"rules" json:"rules"` diff --git a/persistence/album_repository_test.go b/persistence/album_repository_test.go index bbb821ab8..c8a66f8b0 100644 --- a/persistence/album_repository_test.go +++ b/persistence/album_repository_test.go @@ -928,5 +928,13 @@ var _ = Describe("AlbumRepository.UpdateBlurHash", func() { Expect(updated.BlurHashUpdatedAt.Equal(version)).To(BeTrue()) // The targeted update must not touch the row's own timestamps. Expect(updated.UpdatedAt).To(Equal(al.UpdatedAt)) + + // A full-row Put (e.g. a scanner refresh with empty BlurHash fields) must preserve the hash. + updated.BlurHash = "" + updated.BlurHashUpdatedAt = nil + Expect(repo.Put(updated)).To(Succeed()) + after, err := repo.Get(al.ID) + Expect(err).ToNot(HaveOccurred()) + Expect(after.BlurHash).To(Equal("LKO2?U%2Tw=w]~RBVZRi};RPxuwH")) }) })