From f75c180cb6598214fe23bb5bb4c3b1995de9a339 Mon Sep 17 00:00:00 2001 From: Antti Kettunen Date: Tue, 28 Apr 2026 20:49:48 +0300 Subject: [PATCH] Fix download cleanup after wishlist runs - ignore unconfigured backends when clearing completed downloads - keep the post-download cleanup route best-effort after a successful wishlist run - add regression coverage for the orchestrator clear step --- core/download_orchestrator.py | 25 ++++++-- tests/downloads/test_download_orchestrator.py | 61 +++++++++++++++++++ web_server.py | 1 + 3 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 tests/downloads/test_download_orchestrator.py diff --git a/core/download_orchestrator.py b/core/download_orchestrator.py index 2ec28532..41abee9b 100644 --- a/core/download_orchestrator.py +++ b/core/download_orchestrator.py @@ -457,12 +457,25 @@ class DownloadOrchestrator: True if successful """ results = [] - for client in [self.soulseek, self.youtube, self.tidal, self.qobuz, self.hifi, self.deezer_dl, self.lidarr]: - if client: - try: - results.append(await client.clear_all_completed_downloads()) - except Exception: - pass + for name, client in [ + ("soulseek", self.soulseek), + ("youtube", self.youtube), + ("tidal", self.tidal), + ("qobuz", self.qobuz), + ("hifi", self.hifi), + ("deezer_dl", self.deezer_dl), + ("lidarr", self.lidarr), + ]: + if not client: + continue + if hasattr(client, "is_configured") and not client.is_configured(): + logger.debug("Skipping %s clear_all_completed_downloads (not configured)", name) + continue + try: + results.append(await client.clear_all_completed_downloads()) + except Exception as exc: + logger.warning("%s clear_all_completed_downloads failed: %s", name, exc) + results.append(False) return all(results) if results else True diff --git a/tests/downloads/test_download_orchestrator.py b/tests/downloads/test_download_orchestrator.py new file mode 100644 index 00000000..5e06376c --- /dev/null +++ b/tests/downloads/test_download_orchestrator.py @@ -0,0 +1,61 @@ +from core.download_orchestrator import DownloadOrchestrator + + +class _FakeClient: + def __init__(self, configured=True, clear_result=True): + self.configured = configured + self.clear_result = clear_result + self.clear_calls = 0 + + def is_configured(self): + return self.configured + + async def clear_all_completed_downloads(self): + self.clear_calls += 1 + return self.clear_result + + +def _build_orchestrator(**clients): + orch = DownloadOrchestrator.__new__(DownloadOrchestrator) + orch.soulseek = clients.get("soulseek") + orch.youtube = clients.get("youtube") + orch.tidal = clients.get("tidal") + orch.qobuz = clients.get("qobuz") + orch.hifi = clients.get("hifi") + orch.deezer_dl = clients.get("deezer_dl") + orch.lidarr = clients.get("lidarr") + return orch + + +def _run_async(coro): + import asyncio + + loop = asyncio.new_event_loop() + try: + return loop.run_until_complete(coro) + finally: + loop.close() + + +def test_clear_all_completed_downloads_ignores_unconfigured_clients(): + orch = _build_orchestrator( + soulseek=_FakeClient(configured=True, clear_result=True), + youtube=_FakeClient(configured=False, clear_result=False), + ) + + result = _run_async(orch.clear_all_completed_downloads()) + + assert result is True + assert orch.soulseek.clear_calls == 1 + assert orch.youtube.clear_calls == 0 + + +def test_clear_all_completed_downloads_propagates_configured_failures(): + orch = _build_orchestrator( + soulseek=_FakeClient(configured=True, clear_result=False), + ) + + result = _run_async(orch.clear_all_completed_downloads()) + + assert result is False + assert orch.soulseek.clear_calls == 1 diff --git a/web_server.py b/web_server.py index 66803978..30186f86 100644 --- a/web_server.py +++ b/web_server.py @@ -20583,6 +20583,7 @@ def _build_master_deps(): ) + def _run_full_missing_tracks_process(batch_id, playlist_id, tracks_json): return _downloads_master.run_full_missing_tracks_process( batch_id, playlist_id, tracks_json, _build_master_deps()