feat: cover art proxy with disk cache for instant thumbnail loading
Add CoverArtProxy that fetches cover art from CAA, caches the image bytes on disk (~/.local/share/yellowjacket/cover-art-cache/), and returns base64 data URLs via the GetThumbnail Wails binding. First load: fetches from CAA (rate-limited), caches to disk. Subsequent loads: instant from disk cache, no network. 404s: cached as empty files to avoid re-fetching. Frontend explore-view loads thumbnails async via GetThumbnail() calls that fire during render. Cached thumbnails appear as data URLs directly in img src, bypassing the browser's HTTP stack. Uncached thumbnails fall back to the CAA URL while the proxy fetches in the background, then re-render with the cached version. Also stores caa_id and caa_release_mbid in the search index's extra_json for future direct Internet Archive URL construction.
This commit is contained in:
+23
-12
@@ -18,12 +18,13 @@ import (
|
||||
// response cache. Its exported methods form the binding surface
|
||||
// that the frontend calls via generated TypeScript stubs.
|
||||
type Service struct {
|
||||
mb *MusicBrainzClient
|
||||
lb *ListenBrainzClient
|
||||
cache *Cache
|
||||
index *SearchIndex
|
||||
logger *slog.Logger
|
||||
ctx context.Context
|
||||
mb *MusicBrainzClient
|
||||
lb *ListenBrainzClient
|
||||
cache *Cache
|
||||
index *SearchIndex
|
||||
artProxy *CoverArtProxy
|
||||
logger *slog.Logger
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewExploreService creates a Service backed by the given
|
||||
@@ -35,16 +36,18 @@ func NewExploreService(logger *slog.Logger, db *database.DB) *Service {
|
||||
mb := NewMusicBrainzClient(cache, logger.WithGroup("musicbrainz"))
|
||||
lb := NewListenBrainzClient(limiter, cache, logger.WithGroup("listenbrainz"))
|
||||
index := NewSearchIndex(db, lb, logger.WithGroup("search-index"))
|
||||
artProxy := NewCoverArtProxy(limiter)
|
||||
|
||||
logger.Info("explore service created")
|
||||
|
||||
return &Service{
|
||||
mb: mb,
|
||||
lb: lb,
|
||||
cache: cache,
|
||||
index: index,
|
||||
logger: logger,
|
||||
ctx: context.Background(),
|
||||
mb: mb,
|
||||
lb: lb,
|
||||
cache: cache,
|
||||
index: index,
|
||||
artProxy: artProxy,
|
||||
logger: logger,
|
||||
ctx: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +155,14 @@ func (e *Service) CoverArtGroupURL(releaseGroupMBID string) string {
|
||||
return CoverArtGroupURL(releaseGroupMBID)
|
||||
}
|
||||
|
||||
// GetThumbnail returns a base64 data URL for the release group's
|
||||
// cover art. Cached locally on disk — first call fetches from
|
||||
// the Cover Art Archive, subsequent calls are instant.
|
||||
// Returns "" if no cover art is available.
|
||||
func (e *Service) GetThumbnail(releaseGroupMBID string) string {
|
||||
return e.artProxy.GetThumbnail(releaseGroupMBID)
|
||||
}
|
||||
|
||||
// Search concurrently queries MusicBrainz for artists, release
|
||||
// groups, and recordings matching the query, then boosts results
|
||||
// using ListenBrainz popularity data. The final score blends
|
||||
|
||||
Reference in New Issue
Block a user