From 1e0a55a7931ef522448d3e82c6ecfb0621e15c7f Mon Sep 17 00:00:00 2001 From: Deluan Date: Wed, 29 Jul 2026 21:29:16 -0400 Subject: [PATCH] refactor(artwork): name the hashed image store folder for how it is addressed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The content-addressed store sat in artwork/store/, which did not distinguish it from the artist/, playlist/ and radio/ upload folders beside it — those hold artwork too. It is now artwork/hashed/, naming the one property that sets it apart, and the path comes from a consts entry rather than a bare literal, matching how the sibling folders are built. Deliberately not under cache/: that folder holds resizes that rebuild offline from a local source, while this one holds the only local copy of externally fetched images, whose re-fetch depends on a third party still serving them and whose bytes back the stored blurhash and dimensions. No migration: anything left in the old artwork/store/ is orphaned and re-resolved into the new location. --- consts/consts.go | 5 ++++- core/artwork/e2e/resolution_harness_test.go | 3 ++- core/artwork/image_store.go | 3 +-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/consts/consts.go b/consts/consts.go index d7f662cd8..07a61fddb 100644 --- a/consts/consts.go +++ b/consts/consts.go @@ -26,7 +26,7 @@ const ( DBAnalyzeFailureCountKey = "DBAnalyzeFailureCount" // ArtConfFingerprintPropertyKey is the model.PropertyRepository key Backfill compares against // to detect artwork-affecting config changes across restarts. - ArtConfFingerprintPropertyKey = "artwork.fingerprint" + ArtConfFingerprintPropertyKey = "ArtConfFingerprint" UIAuthorizationHeader = "X-ND-Authorization" UIClientUniqueIDHeader = "X-ND-Client-Unique-Id" @@ -86,6 +86,9 @@ const ( I18nFolder = "i18n" ScanIgnoreFile = ".ndignore" ArtworkFolder = "artwork" + // HashedArtworkFolder is a subtree of ArtworkFolder, kept apart from the name-addressed + // upload folders beside it so Prune's sweep never reaches them. + HashedArtworkFolder = "hashed" PlaceholderArtistArt = "artist-placeholder.webp" PlaceholderAlbumArt = "album-placeholder.webp" diff --git a/core/artwork/e2e/resolution_harness_test.go b/core/artwork/e2e/resolution_harness_test.go index 4021f9ef6..78836df7f 100644 --- a/core/artwork/e2e/resolution_harness_test.go +++ b/core/artwork/e2e/resolution_harness_test.go @@ -20,6 +20,7 @@ import ( _ "github.com/navidrome/navidrome/adapters/gotaglib" "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/conf/configtest" + "github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/core/agents" "github.com/navidrome/navidrome/core/artwork" "github.com/navidrome/navidrome/core/metrics" @@ -110,7 +111,7 @@ func setupResolutionHarness() { storagetest.Register(fakeLibScheme, fakeFS) ffm := tests.NewMockFFmpeg("") - rstore = artwork.NewImageStore(filepath.Join(tempDir, "store")) + rstore = artwork.NewImageStore(filepath.Join(tempDir, consts.HashedArtworkFolder)) // size=0 requests stream originals, so this reader is never called (serving_test covers resizing). imgCache := cache.NewFileCache("ArtworkResolutionE2E", "100MB", "images", 0, func(context.Context, cache.Item) (io.Reader, error) { diff --git a/core/artwork/image_store.go b/core/artwork/image_store.go index bac20360f..72cb34ceb 100644 --- a/core/artwork/image_store.go +++ b/core/artwork/image_store.go @@ -25,9 +25,8 @@ func NewImageStore(rootDir string) *ImageStore { return &ImageStore{root: rootDir} } -// GetImageStore roots the store in its own subtree so Prune's sweep never reaches the upload folders beside it. func GetImageStore() *ImageStore { - return NewImageStore(filepath.Join(conf.Server.DataFolder.String(), consts.ArtworkFolder, "store")) + return NewImageStore(filepath.Join(conf.Server.DataFolder.String(), consts.ArtworkFolder, consts.HashedArtworkFolder)) } // extForMime must stay stable across OSes: extensions are baked into stored paths and re-derived on Open.