diff --git a/somafm_tui/app.py b/somafm_tui/app.py index 44851da..8b1bbc3 100644 --- a/somafm_tui/app.py +++ b/somafm_tui/app.py @@ -139,6 +139,7 @@ class SomaFMApp(App[None]): self.artwork_bytes: bytes | None = None self._art_track_key = "" self._bandcamp_key = "" + self._bandcamp_urls: dict[str, str] = {} self._http: httpx.AsyncClient | None = None self._closed = False self._tick_timer = None @@ -376,6 +377,10 @@ class SomaFMApp(App[None]): previous = station.now_playing station.history = songs station.now_playing = songs[0] + if previous is not None and previous.key() == songs[0].key(): + # History refreshes replace the Song object even when the track has + # not changed. Keep the asynchronously discovered Bandcamp link. + songs[0].bandcamp_url = previous.bandcamp_url changed = previous is None or previous.key() != songs[0].key() if self.current is not None and self.current.id == station_id: if changed: @@ -432,13 +437,24 @@ class SomaFMApp(App[None]): def _update_bandcamp(self) -> None: """Find a Bandcamp track link once per now-playing track.""" station = self.current - if station is None or self.offline or self._http is None: + if station is None: return song = station.now_playing if song is None or not (song.artist.strip() or song.title.strip()): return track_key = f"{station.id}|{song.key()}" + cached_url = self._bandcamp_urls.get(track_key) + if cached_url: + self._bandcamp_key = track_key + if song.bandcamp_url != cached_url: + song.bandcamp_url = cached_url + with contextlib.suppress(NoMatches): + self.query_one(StagePanel).refresh_stage() + return + + if self.offline or self._http is None: + return if track_key == self._bandcamp_key: return self._bandcamp_key = track_key @@ -461,6 +477,7 @@ class SomaFMApp(App[None]): if not url: return + self._bandcamp_urls[f"{station_id}|{song_key}"] = url station = next((s for s in self.stations if s.id == station_id), None) if station is None or self.current is None or self.current.id != station_id: return @@ -822,6 +839,7 @@ class SomaFMApp(App[None]): def action_refresh(self) -> None: self._last_history_fetch = 0.0 self._bandcamp_key = "" + self._bandcamp_urls.clear() self._set_notice("refreshing…", 2) if self.offline: self._set_notice("offline mode — nothing to refresh", 3) diff --git a/tests/test_app.py b/tests/test_app.py index 16a7253..af0df46 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -159,6 +159,63 @@ async def test_stage_shows_bandcamp_link_only_when_present() -> None: assert "Bandcamp" not in rendered_text(app) +@pytest.mark.asyncio +async def test_bandcamp_link_survives_history_refresh(monkeypatch) -> None: + app = SomaFMApp(autoplay=False, offline=True, audio=False) + async with app.run_test(size=(110, 40)) as pilot: + await pilot.pause() + station = app.current + assert station is not None + url = "https://jensbuchert.bandcamp.com/track/rauschebart" + old_song = Song( + title="Rauschebart", + artist="Jens Buchert", + album="Gravity", + bandcamp_url=url, + ) + station.now_playing = old_song + station.history = [old_song] + + async def fake_fetch_history(client, station_id): + assert station_id == station.id + return [ + Song( + title="Rauschebart", + artist="Jens Buchert", + album="Gravity", + ) + ] + + monkeypatch.setattr("somafm_tui.app.api.fetch_history", fake_fetch_history) + worker = app._fetch_history(station.id) + await worker.wait() + await pilot.pause() + + assert station.now_playing is not None + assert station.now_playing is not old_song + assert station.now_playing.bandcamp_url == url + assert "Bandcamp" in rendered_text(app) + + +@pytest.mark.asyncio +async def test_cached_bandcamp_link_is_reapplied_to_new_song() -> None: + app = SomaFMApp(autoplay=False, offline=True, audio=False) + async with app.run_test(size=(110, 40)) as pilot: + await pilot.pause() + station = app.current + assert station is not None + url = "https://jensbuchert.bandcamp.com/track/rauschebart" + song = Song(title="Rauschebart", artist="Jens Buchert", album="Gravity") + app._bandcamp_urls[f"{station.id}|{song.key()}"] = url + station.now_playing = song + + app._update_bandcamp() + await pilot.pause() + + assert song.bandcamp_url == url + assert "Bandcamp" in rendered_text(app) + + @pytest.mark.asyncio async def test_media_session_commands_drive_the_app() -> None: app = SomaFMApp(autoplay=False, offline=True, audio=False)