From 58063f5d7c043dc4784d34d2cf910af1c1669d42 Mon Sep 17 00:00:00 2001 From: Rob Martin Date: Sun, 4 Oct 2026 11:32:32 -0600 Subject: [PATCH] fix(debridlink): reuse the file list from the status check instead of fetching it again --- shelfmark/download/clients/debridlink.py | 15 ++++++++++---- tests/prowlarr/test_debridlink_client.py | 26 ++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/shelfmark/download/clients/debridlink.py b/shelfmark/download/clients/debridlink.py index 7c72fbd0e..fc4bfa0de 100644 --- a/shelfmark/download/clients/debridlink.py +++ b/shelfmark/download/clients/debridlink.py @@ -592,7 +592,7 @@ def _handle_torrent_info(self, info: dict[str, Any], state: _DownloadState) -> D and all(_as_float(f.get("downloadPercent", 100)) >= _COMPLETE_PERCENT for f in files) ) if ready: - self._maybe_start_download_thread(state) + self._maybe_start_download_thread(state, files) return DownloadStatus( progress=50.0, state=DownloadState.DOWNLOADING, @@ -657,8 +657,15 @@ def _start_waiting_torrent(self, state: _DownloadState) -> None: with state.lock: state.start_requested = False - def _maybe_start_download_thread(self, state: _DownloadState) -> None: - """Spawn a background thread to download the seedbox's files.""" + def _maybe_start_download_thread( + self, state: _DownloadState, files: list[dict[str, Any]] | None = None + ) -> None: + """Spawn a background thread to download the seedbox's files. + + ``files`` is the list the status check already fetched by ``ids``; passing it on + saves the thread a second request that has none of the status path's flood and + transient-error handling. + """ with state.lock: already_running = state.phase in ("downloading_http", "complete") thread_alive = state.download_thread is not None and state.download_thread.is_alive() @@ -670,7 +677,7 @@ def _maybe_start_download_thread(self, state: _DownloadState) -> None: state.progress = 50.0 t = threading.Thread( target=self._process_and_download, - args=(state,), + args=(state, files), daemon=True, ) state.download_thread = t diff --git a/tests/prowlarr/test_debridlink_client.py b/tests/prowlarr/test_debridlink_client.py index 9cd3d2997..42c026b20 100644 --- a/tests/prowlarr/test_debridlink_client.py +++ b/tests/prowlarr/test_debridlink_client.py @@ -256,7 +256,7 @@ def test_completion_hands_off_only_when_every_file_is_ready(self, tmp_path, monk status = client._handle_torrent_info(partial, state) assert status.progress == 50.0 - started.assert_called_once_with(state) + started.assert_called_once_with(state, partial["files"]) def test_an_empty_file_list_is_not_treated_as_ready(self, tmp_path, monkeypatch): client = self._bare_client() @@ -326,7 +326,7 @@ def test_retrieval_starts_at_half_progress_rather_than_zero(self, tmp_path, monk state = _state(tmp_path) DebridLinkClient._downloads["DL1"] = state release = threading.Event() - monkeypatch.setattr(client, "_process_and_download", lambda _state: release.wait(5)) + monkeypatch.setattr(client, "_process_and_download", lambda _state, _files: release.wait(5)) client._maybe_start_download_thread(state) try: @@ -339,6 +339,28 @@ def test_retrieval_starts_at_half_progress_rather_than_zero(self, tmp_path, monk assert status.message == "Downloading files via HTTP..." assert status.progress == 50.0 + def test_retrieval_reuses_the_files_the_status_check_already_has(self, tmp_path, monkeypatch): + client = self._bare_client() + state = _state(tmp_path) + DebridLinkClient._downloads["DL1"] = state + refetched = MagicMock(side_effect=AssertionError("file list fetched a second time")) + monkeypatch.setattr(client, "_fetch_file_list", refetched) + seen: list[list[dict]] = [] + monkeypatch.setattr( + client, "_process_and_download", lambda _state, files=None: seen.append(files) + ) + + ready = { + "downloadPercent": 100, + "files": [{"name": "Dune.epub", "downloadPercent": 100, "downloadUrl": "https://dl/x"}], + } + client._handle_torrent_info(ready, state) + assert state.download_thread is not None + state.download_thread.join(5) + + assert seen == [ready["files"]] + refetched.assert_not_called() + def test_a_vanished_torrent_becomes_an_error_rather_than_a_stall(self, monkeypatch, tmp_path): monkeypatch.setattr("shelfmark.download.clients.debridlink.TMP_DIR", tmp_path) _mock_request(monkeypatch, {"success": True, "value": []})