quintodrome/core/artwork/cache_warmer.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

121 lines
2.7 KiB
Go
Raw Normal View History

package artwork
2022-12-23 08:28:22 -09:00
import (
"context"
"fmt"
"io"
2022-12-28 09:03:41 -09:00
"sync"
"time"
2022-12-23 08:28:22 -09:00
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
2022-12-28 09:03:41 -09:00
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/utils/cache"
2022-12-23 08:28:22 -09:00
"github.com/navidrome/navidrome/utils/pl"
"golang.org/x/exp/maps"
2022-12-23 08:28:22 -09:00
)
type CacheWarmer interface {
2022-12-23 08:28:22 -09:00
PreCache(artID model.ArtworkID)
}
func NewCacheWarmer(artwork Artwork, cache cache.FileCache) CacheWarmer {
2022-12-23 08:28:22 -09:00
// If image cache is disabled, return a NOOP implementation
if conf.Server.ImageCacheSize == "0" {
return &noopCacheWarmer{}
}
a := &cacheWarmer{
2022-12-28 09:03:41 -09:00
artwork: artwork,
cache: cache,
buffer: make(map[string]struct{}),
2022-12-28 09:03:41 -09:00
wakeSignal: make(chan struct{}, 1),
2022-12-23 08:28:22 -09:00
}
2022-12-28 09:03:41 -09:00
// Create a context with a fake admin user, to be able to pre-cache Playlist CoverArts
ctx := request.WithUser(context.TODO(), model.User{IsAdmin: true})
go a.run(ctx)
2022-12-23 08:28:22 -09:00
return a
}
type cacheWarmer struct {
2022-12-28 09:03:41 -09:00
artwork Artwork
buffer map[string]struct{}
2022-12-28 09:03:41 -09:00
mutex sync.Mutex
cache cache.FileCache
2022-12-28 09:03:41 -09:00
wakeSignal chan struct{}
2022-12-23 08:28:22 -09:00
}
func (a *cacheWarmer) PreCache(artID model.ArtworkID) {
2022-12-28 09:03:41 -09:00
a.mutex.Lock()
defer a.mutex.Unlock()
a.buffer[artID.String()] = struct{}{}
2022-12-28 09:03:41 -09:00
a.sendWakeSignal()
}
func (a *cacheWarmer) sendWakeSignal() {
// Don't block if the previous signal was not read yet
select {
case a.wakeSignal <- struct{}{}:
default:
}
2022-12-23 08:28:22 -09:00
}
func (a *cacheWarmer) run(ctx context.Context) {
2022-12-28 09:03:41 -09:00
for {
time.AfterFunc(10*time.Second, func() {
2022-12-28 09:03:41 -09:00
a.sendWakeSignal()
})
<-a.wakeSignal
// 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
}
2022-12-28 09:03:41 -09:00
a.mutex.Lock()
// If there's nothing to send, keep waiting
if len(a.buffer) == 0 {
a.mutex.Unlock()
continue
2022-12-28 09:03:41 -09:00
}
batch := maps.Keys(a.buffer)
a.buffer = make(map[string]struct{})
2022-12-28 09:03:41 -09:00
a.mutex.Unlock()
a.processBatch(ctx, batch)
2022-12-28 09:03:41 -09:00
}
}
func (a *cacheWarmer) processBatch(ctx context.Context, batch []string) {
log.Trace(ctx, "PreCaching a new batch of artwork", "batchSize", len(batch))
input := pl.FromSlice(ctx, batch)
errs := pl.Sink(ctx, 2, input, a.doCacheImage)
2022-12-23 08:28:22 -09:00
for err := range errs {
log.Warn(ctx, "Error warming cache", err)
}
}
func (a *cacheWarmer) doCacheImage(ctx context.Context, id string) error {
2022-12-27 08:54:51 -09:00
r, _, err := a.artwork.Get(ctx, id, 0)
2022-12-23 08:28:22 -09:00
if err != nil {
return fmt.Errorf("error cacheing id='%s': %w", id, err)
}
defer r.Close()
_, err = io.Copy(io.Discard, r)
if err != nil {
return err
}
return nil
}
type noopCacheWarmer struct{}
func (a *noopCacheWarmer) PreCache(model.ArtworkID) {}