Avoid repeated artwork miss fetches and refresh churn

Lowers CPU usage:
- Only call refresh() when real artwork is found
- Treating NoArtwork as a valid cached result (art is not None check).
- Adding in-flight dedupe (pending_tracks) so the same track doesn't spawn parallel fetches.
This commit is contained in:
Götz 2026-02-28 19:14:40 -05:00
parent 9b910cd991
commit fa82f45ef9

View file

@ -30,19 +30,28 @@ class MpdArtworkCache:
mpd: MpdStateHandler
album_cache: Cache[Artwork]
track_cache: Cache[Artwork]
pending_tracks: set[str]
def __init__(self, mpd: MpdStateHandler, cache_url: URL = MEMORY):
self.mpd = mpd
self.album_cache = make_cache(ArtworkSchema, cache_url, "album")
self.track_cache = make_cache(ArtworkSchema, cache_url, "track")
self.pending_tracks = set()
async def get_cached_artwork(self, song: CurrentSongResponse) -> bytes | None:
art = await self.track_cache.get(calc_track_key(song))
if art:
return art.data
track_key = calc_track_key(song)
art = await self.track_cache.get(track_key)
if art is not None:
# NoArtwork is a valid cached value too: returning None here avoids
# repeatedly re-querying MPD for files that have no embedded art.
if art:
return art.data
return None
# If we don't have track artwork cached, go find some.
run_background_task(self.cache_artwork(song))
if track_key not in self.pending_tracks:
self.pending_tracks.add(track_key)
run_background_task(self.cache_artwork(song))
# Even if we don't have cached track art, we can try looking for cached album art.
art = await self.album_cache.get(calc_album_key(song))
@ -52,10 +61,16 @@ class MpdArtworkCache:
return None
async def cache_artwork(self, song: CurrentSongResponse) -> None:
art = to_artwork(await self.mpd.get_art(song["file"]))
track_key = calc_track_key(song)
try:
await self.album_cache.add(calc_album_key(song), art, ttl=CACHE_TTL)
except ValueError:
pass
await self.track_cache.set(calc_track_key(song), art, ttl=CACHE_TTL)
await self.mpd.refresh()
art = to_artwork(await self.mpd.get_art(song["file"]))
try:
await self.album_cache.add(calc_album_key(song), art, ttl=CACHE_TTL)
except ValueError:
pass
await self.track_cache.set(track_key, art, ttl=CACHE_TTL)
# Refresh receivers only when we discovered actual artwork.
if art:
await self.mpd.refresh()
finally:
self.pending_tracks.discard(track_key)