Fix artwork caching

This commit is contained in:
Deluan
2022-12-27 12:54:51 -05:00
committed by Deluan Quintão
parent 92ddae4a65
commit 722a00cacf
5 changed files with 52 additions and 27 deletions
+29 -15
View File
@@ -19,7 +19,7 @@ import (
) )
type Artwork interface { type Artwork interface {
Get(ctx context.Context, id string, size int) (io.ReadCloser, error) Get(ctx context.Context, id string, size int) (io.ReadCloser, time.Time, error)
} }
func NewArtwork(ds model.DataStore, cache cache.FileCache, ffmpeg ffmpeg.FFmpeg) Artwork { func NewArtwork(ds model.DataStore, cache cache.FileCache, ffmpeg ffmpeg.FFmpeg) Artwork {
@@ -38,7 +38,7 @@ type artworkReader interface {
Reader(ctx context.Context) (io.ReadCloser, string, error) Reader(ctx context.Context) (io.ReadCloser, string, error)
} }
func (a *artwork) Get(ctx context.Context, id string, size int) (reader io.ReadCloser, err error) { func (a *artwork) Get(ctx context.Context, id string, size int) (reader io.ReadCloser, lastUpdate time.Time, err error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second) ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel() defer cancel()
@@ -46,11 +46,28 @@ func (a *artwork) Get(ctx context.Context, id string, size int) (reader io.ReadC
if id != "" { if id != "" {
artID, err = model.ParseArtworkID(id) artID, err = model.ParseArtworkID(id)
if err != nil { if err != nil {
return nil, errors.New("invalid ID") return nil, time.Time{}, errors.New("invalid ID")
} }
} }
artReader, err := a.getArtworkReader(ctx, artID, size)
if err != nil {
return nil, time.Time{}, err
}
r, err := a.cache.Get(ctx, artReader)
if err != nil && !errors.Is(err, context.Canceled) {
log.Error(ctx, "Error accessing image cache", "id", id, "size", size, err)
}
return r, artReader.LastUpdated(), err
}
func (a *artwork) getArtworkReader(ctx context.Context, artID model.ArtworkID, size int) (artworkReader, error) {
var artReader artworkReader var artReader artworkReader
var err error
if size > 0 {
artReader, err = resizedFromOriginal(ctx, a, artID, size)
} else {
switch artID.Kind { switch artID.Kind {
case model.KindAlbumArtwork: case model.KindAlbumArtwork:
artReader, err = newAlbumArtworkReader(ctx, a, artID) artReader, err = newAlbumArtworkReader(ctx, a, artID)
@@ -59,18 +76,8 @@ func (a *artwork) Get(ctx context.Context, id string, size int) (reader io.ReadC
default: default:
artReader, err = newEmptyIDReader(ctx, artID) artReader, err = newEmptyIDReader(ctx, artID)
} }
if err != nil {
return nil, err
} }
if size > 0 { return artReader, err
artReader = resizedFromOriginal(artReader, artID, size)
}
r, err := a.cache.Get(ctx, artReader)
if err != nil && !errors.Is(err, context.Canceled) {
log.Error(ctx, "Error accessing image cache", "id", id, "size", size, err)
}
return r, err
} }
type cacheItem struct { type cacheItem struct {
@@ -80,7 +87,14 @@ type cacheItem struct {
} }
func (i *cacheItem) Key() string { func (i *cacheItem) Key() string {
return fmt.Sprintf("%s.%d.%d.%d", i.artID.ID, i.lastUpdate.UnixMilli(), i.size, conf.Server.CoverJpegQuality) return fmt.Sprintf(
"%s.%d.%d.%d.%t",
i.artID.ID,
i.lastUpdate.UnixMilli(),
i.size,
conf.Server.CoverJpegQuality,
conf.Server.DevFastAccessCoverArt,
)
} }
type imageCache struct { type imageCache struct {
+1 -1
View File
@@ -46,7 +46,7 @@ func (a *cacheWarmer) run(ctx context.Context) {
} }
func (a *cacheWarmer) doCacheImage(ctx context.Context, id string) error { func (a *cacheWarmer) doCacheImage(ctx context.Context, id string) error {
r, err := a.artwork.Get(ctx, id, 0) r, _, err := a.artwork.Get(ctx, id, 0)
if err != nil { if err != nil {
return fmt.Errorf("error cacheing id='%s': %w", id, err) return fmt.Errorf("error cacheing id='%s': %w", id, err)
} }
+1 -1
View File
@@ -56,7 +56,7 @@ func (a *mediafileArtworkReader) Reader(ctx context.Context) (io.ReadCloser, str
func fromAlbum(ctx context.Context, a *artwork, id model.ArtworkID) sourceFunc { func fromAlbum(ctx context.Context, a *artwork, id model.ArtworkID) sourceFunc {
return func() (io.ReadCloser, string, error) { return func() (io.ReadCloser, string, error) {
r, err := a.Get(ctx, id.String(), 0) r, _, err := a.Get(ctx, id.String(), 0)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
+15 -6
View File
@@ -21,15 +21,21 @@ import (
type resizedArtworkReader struct { type resizedArtworkReader struct {
cacheItem cacheItem
original artworkReader a *artwork
} }
func resizedFromOriginal(original artworkReader, artID model.ArtworkID, size int) *resizedArtworkReader { func resizedFromOriginal(ctx context.Context, a *artwork, artID model.ArtworkID, size int) (*resizedArtworkReader, error) {
r := &resizedArtworkReader{original: original} r := &resizedArtworkReader{a: a}
r.cacheItem.artID = artID r.cacheItem.artID = artID
r.cacheItem.size = size r.cacheItem.size = size
// Get lastUpdated from original artwork
original, err := a.getArtworkReader(ctx, artID, 0)
if err != nil {
return nil, err
}
r.cacheItem.lastUpdate = original.LastUpdated() r.cacheItem.lastUpdate = original.LastUpdated()
return r return r, nil
} }
func (a *resizedArtworkReader) LastUpdated() time.Time { func (a *resizedArtworkReader) LastUpdated() time.Time {
@@ -37,13 +43,16 @@ func (a *resizedArtworkReader) LastUpdated() time.Time {
} }
func (a *resizedArtworkReader) Reader(ctx context.Context) (io.ReadCloser, string, error) { func (a *resizedArtworkReader) Reader(ctx context.Context) (io.ReadCloser, string, error) {
orig, path, err := a.original.Reader(ctx) // Get artwork in original size, possibly from cache
orig, _, err := a.a.Get(ctx, a.artID.String(), 0)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
// Keep a copy of the original data. In case we can't resize it, send it as is // Keep a copy of the original data. In case we can't resize it, send it as is
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
r := io.TeeReader(orig, buf) r := io.TeeReader(orig, buf)
defer orig.Close()
resized, origSize, err := resizeImage(r, a.size) resized, origSize, err := resizeImage(r, a.size)
log.Trace(ctx, "Resizing artwork", "artID", a.artID, "original", origSize, "resized", a.size) log.Trace(ctx, "Resizing artwork", "artID", a.artID, "original", origSize, "resized", a.size)
@@ -53,7 +62,7 @@ func (a *resizedArtworkReader) Reader(ctx context.Context) (io.ReadCloser, strin
_, _ = io.Copy(io.Discard, r) _, _ = io.Copy(io.Discard, r)
return io.NopCloser(buf), "", nil return io.NopCloser(buf), "", nil
} }
return io.NopCloser(resized), fmt.Sprintf("%s@%d", path, a.size), nil return io.NopCloser(resized), fmt.Sprintf("%s@%d", a.artID, a.size), nil
} }
func asImageReader(r io.Reader) (io.Reader, string, error) { func asImageReader(r io.Reader) (io.Reader, string, error) {
+3 -1
View File
@@ -6,6 +6,7 @@ import (
"io" "io"
"net/http" "net/http"
"regexp" "regexp"
"time"
"github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/consts"
@@ -55,9 +56,10 @@ func (api *Router) GetCoverArt(w http.ResponseWriter, r *http.Request) (*respons
id := utils.ParamString(r, "id") id := utils.ParamString(r, "id")
size := utils.ParamInt(r, "size", 0) size := utils.ParamInt(r, "size", 0)
imgReader, lastUpdate, err := api.artwork.Get(r.Context(), id, size)
w.Header().Set("cache-control", "public, max-age=315360000") w.Header().Set("cache-control", "public, max-age=315360000")
w.Header().Set("last-modified", lastUpdate.Format(time.RFC1123))
imgReader, err := api.artwork.Get(r.Context(), id, size)
switch { switch {
case errors.Is(err, context.Canceled): case errors.Is(err, context.Canceled):
return nil, nil return nil, nil