From cc14485194125866491a45d7b6a10e9a88ab5a7d Mon Sep 17 00:00:00 2001
From: Deluan <deluan@navidrome.org>
Date: Wed, 28 Dec 2022 23:01:52 -0500
Subject: [PATCH] When trying to PreCache, wait for ImageCache to be available

---
 cmd/wire_gen.go              |  2 +-
 core/artwork/cache_warmer.go | 41 ++++++++++++++++++++++++------------
 utils/cache/file_caches.go   |  2 +-
 3 files changed, 30 insertions(+), 15 deletions(-)

diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go
index 6cacfe2c7..49600c84a 100644
--- a/cmd/wire_gen.go
+++ b/cmd/wire_gen.go
@@ -84,7 +84,7 @@ func createScanner() scanner.Scanner {
 	fileCache := artwork.GetImageCache()
 	fFmpeg := ffmpeg.New()
 	artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg)
-	cacheWarmer := artwork.NewCacheWarmer(artworkArtwork)
+	cacheWarmer := artwork.NewCacheWarmer(artworkArtwork, fileCache)
 	broker := events.GetBroker()
 	scannerScanner := scanner.New(dataStore, playlists, cacheWarmer, broker)
 	return scannerScanner
diff --git a/core/artwork/cache_warmer.go b/core/artwork/cache_warmer.go
index 0ad337470..677f01ce4 100644
--- a/core/artwork/cache_warmer.go
+++ b/core/artwork/cache_warmer.go
@@ -11,14 +11,16 @@ import (
 	"github.com/navidrome/navidrome/log"
 	"github.com/navidrome/navidrome/model"
 	"github.com/navidrome/navidrome/model/request"
+	"github.com/navidrome/navidrome/utils/cache"
 	"github.com/navidrome/navidrome/utils/pl"
+	"golang.org/x/exp/maps"
 )
 
 type CacheWarmer interface {
 	PreCache(artID model.ArtworkID)
 }
 
-func NewCacheWarmer(artwork Artwork) CacheWarmer {
+func NewCacheWarmer(artwork Artwork, cache cache.FileCache) CacheWarmer {
 	// If image cache is disabled, return a NOOP implementation
 	if conf.Server.ImageCacheSize == "0" {
 		return &noopCacheWarmer{}
@@ -26,6 +28,8 @@ func NewCacheWarmer(artwork Artwork) CacheWarmer {
 
 	a := &cacheWarmer{
 		artwork:    artwork,
+		cache:      cache,
+		buffer:     make(map[string]struct{}),
 		wakeSignal: make(chan struct{}, 1),
 	}
 
@@ -37,15 +41,16 @@ func NewCacheWarmer(artwork Artwork) CacheWarmer {
 
 type cacheWarmer struct {
 	artwork    Artwork
-	buffer     []string
+	buffer     map[string]struct{}
 	mutex      sync.Mutex
+	cache      cache.FileCache
 	wakeSignal chan struct{}
 }
 
 func (a *cacheWarmer) PreCache(artID model.ArtworkID) {
 	a.mutex.Lock()
 	defer a.mutex.Unlock()
-	a.buffer = append(a.buffer, artID.String())
+	a.buffer[artID.String()] = struct{}{}
 	a.sendWakeSignal()
 }
 
@@ -59,22 +64,32 @@ func (a *cacheWarmer) sendWakeSignal() {
 
 func (a *cacheWarmer) run(ctx context.Context) {
 	for {
-		time.AfterFunc(5*time.Second, func() {
+		time.AfterFunc(10*time.Second, func() {
 			a.sendWakeSignal()
 		})
 		<-a.wakeSignal
 
-		a.mutex.Lock()
-		var batch []string
-		if len(a.buffer) > 0 {
-			batch = a.buffer
-			a.buffer = nil
+		// If cache not available, keep waiting
+		if !a.cache.Available(ctx) {
+			if len(a.buffer) > 0 {
+				log.Trace(ctx, "Cache not available, buffering precache request", "bufferLen", len(a.buffer))
+			}
+			continue
 		}
+
+		a.mutex.Lock()
+
+		// If there's nothing to send, keep waiting
+		if len(a.buffer) == 0 {
+			a.mutex.Unlock()
+			continue
+		}
+
+		batch := maps.Keys(a.buffer)
+		a.buffer = make(map[string]struct{})
 		a.mutex.Unlock()
 
-		if len(batch) > 0 {
-			a.processBatch(ctx, batch)
-		}
+		a.processBatch(ctx, batch)
 	}
 }
 
@@ -102,4 +117,4 @@ func (a *cacheWarmer) doCacheImage(ctx context.Context, id string) error {
 
 type noopCacheWarmer struct{}
 
-func (a *noopCacheWarmer) PreCache(id model.ArtworkID) {}
+func (a *noopCacheWarmer) PreCache(model.ArtworkID) {}
diff --git a/utils/cache/file_caches.go b/utils/cache/file_caches.go
index 633f7bd10..b91dd0ad4 100644
--- a/utils/cache/file_caches.go
+++ b/utils/cache/file_caches.go
@@ -70,7 +70,7 @@ type fileCache struct {
 	mutex       *sync.RWMutex
 }
 
-func (fc *fileCache) Ready(ctx context.Context) bool {
+func (fc *fileCache) Ready(_ context.Context) bool {
 	fc.mutex.RLock()
 	defer fc.mutex.RUnlock()
 	return fc.ready