From fa82f45ef97c07c1c64bde8f873e7a7d9555afd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6tz?= Date: Sat, 28 Feb 2026 19:14:40 -0500 Subject: [PATCH] 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. --- src/mpd_now_playable/mpd/artwork_cache.py | 35 ++++++++++++++++------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/mpd_now_playable/mpd/artwork_cache.py b/src/mpd_now_playable/mpd/artwork_cache.py index 0b1ebbe..7948b50 100644 --- a/src/mpd_now_playable/mpd/artwork_cache.py +++ b/src/mpd_now_playable/mpd/artwork_cache.py @@ -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)