From 9ed2d8502b851f5584a1918e20bebd1c6f9a8096 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 1 Mar 2024 08:45:36 -0800 Subject: [PATCH] add GetFullSizeCoverArtAsync --- backend/imagemanager.go | 59 ++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/backend/imagemanager.go b/backend/imagemanager.go index 96da5bf..f8102ee 100644 --- a/backend/imagemanager.go +++ b/backend/imagemanager.go @@ -136,20 +136,26 @@ func (i *ImageManager) GetFullSizeCoverArt(coverID string) (image.Image, error) i.cachedFullSizeCoverAccessedAt = time.Now().UnixMilli() return i.cachedFullSizeCover, nil } - if i.s.Server == nil { - return nil, errors.New("logged out") - } + return i.getFullSizeCoverArtFromServer(context.Background(), coverID, nil) +} - i.serverFetchSema <- struct{}{} // acquire - im, err := i.s.Server.GetCoverArt(coverID, 0) - <-i.serverFetchSema // release - if err != nil { - return nil, err - } - i.cachedFullSizeCover = im - i.cachedFullSizeCoverID = coverID - i.cachedFullSizeCoverAccessedAt = time.Now().UnixMilli() - return im, nil +// GetCoverThumbnailAsync asynchronously fetches the cover image for the given ID, +// and invokes the callback on completion. It returns a context.CancelFunc which can be used to +// cancel the fetch. The callback will not be invoked if the fetch is cancelled before completion. +// The cancel func must be invoked to avoid resource leaks. Use GetCoverThumbnail if cancellation is not needed. +func (i *ImageManager) GetFullSizeCoverArtAsync(coverID string, cb func(image.Image, error)) context.CancelFunc { + ctx, cancel := context.WithCancel(context.Background()) + go func() { + if i.cachedFullSizeCoverID == coverID { + i.cachedFullSizeCoverAccessedAt = time.Now().UnixMilli() + if ctx.Err() == nil { + cb(i.cachedFullSizeCover, nil) + } + } else { + i.getFullSizeCoverArtFromServer(ctx, coverID, cb) + } + }() + return cancel } // GetCoverArtURL returns the URL for the locally cached cover thumbnail, if it exists. @@ -267,6 +273,33 @@ func (i *ImageManager) fetchAndCacheCoverFromServer(ctx context.Context, coverID } } +func (i *ImageManager) getFullSizeCoverArtFromServer(ctx context.Context, coverID string, cb func(image.Image, error)) (image.Image, error) { + if i.s.Server == nil { + err := errors.New("logged out") + if ctx.Err() == nil && cb != nil { + cb(nil, err) + } + return nil, err + } + + select { + case <-ctx.Done(): + return nil, context.Canceled + case i.serverFetchSema <- struct{}{}: // acquire + im, err := i.s.Server.GetCoverArt(coverID, 0) + <-i.serverFetchSema // release + if err == nil { + i.cachedFullSizeCover = im + i.cachedFullSizeCoverID = coverID + i.cachedFullSizeCoverAccessedAt = time.Now().UnixMilli() + } + if ctx.Err() == nil && cb != nil { + cb(im, err) + } + return im, err + } +} + func (i *ImageManager) checkRefreshLocalCover(stat os.FileInfo, coverID string, ttl time.Duration) { if time.Since(stat.ModTime()) > cachedImageValidTime { i.fetchAndCacheCoverFromServer(context.Background(), coverID, ttl, nil)