From 9f11e83e1f2ca92d5deffb3d1763c755daf99019 Mon Sep 17 00:00:00 2001 From: Matt Van Horn Date: Thu, 3 Sep 2026 20:19:50 -0700 Subject: [PATCH] fix: keep polling queued Real-Debrid torrents (#1303) Add `queued` to the existing set of non-terminal Real-Debrid torrent states so `_handle_torrent_info` returns an in-progress `DownloadStatus` and leaves the mutable download state eligible for subsequent polling. Keep the change within the existing status-classification path rather than introducing a new helper or changing the broader handling of unknown statuses. The native Real-Debrid client currently treats the documented `queued` torrent status as a terminal error because it is absent from `_STATUS_DOWNLOADING`. This occurs after a torrent has been added and its files selected, particularly for uncached torrents that wait before downloading. A torrent-info payload with `status: queued`, zero progress, and a filename returns a non-complete `DownloadState.DOWNLOADING` result rather than `DownloadState.ERROR`; After handling `queued`, the internal download state remains non-terminal so a later status poll can be processed instead of returning a cached error. Fixes #1268 Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> --- shelfmark/download/clients/realdebrid.py | 1 + tests/prowlarr/test_debrid_clients.py | 51 +++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/shelfmark/download/clients/realdebrid.py b/shelfmark/download/clients/realdebrid.py index 8c155ea7..0d576155 100644 --- a/shelfmark/download/clients/realdebrid.py +++ b/shelfmark/download/clients/realdebrid.py @@ -50,6 +50,7 @@ _STATUS_DOWNLOADING = frozenset( { "magnet_conversion", "waiting_files_selection", + "queued", "downloading", "compressing", "uploading", diff --git a/tests/prowlarr/test_debrid_clients.py b/tests/prowlarr/test_debrid_clients.py index 1b7f637d..15bb54ff 100644 --- a/tests/prowlarr/test_debrid_clients.py +++ b/tests/prowlarr/test_debrid_clients.py @@ -10,8 +10,9 @@ from unittest.mock import MagicMock import pytest +from shelfmark.download.clients import DownloadState from shelfmark.download.clients.alldebrid import AllDebridClient -from shelfmark.download.clients.realdebrid import RealDebridClient +from shelfmark.download.clients.realdebrid import RealDebridClient, _DownloadState from shelfmark.download.clients.torrent_utils import ( DebridMagnet, DebridTorrentFile, @@ -149,6 +150,54 @@ class TestRealDebridAdd: put.assert_not_called() +class TestRealDebridStatus: + @staticmethod + def _client(): + return RealDebridClient.__new__(RealDebridClient) + + @staticmethod + def _state(tmp_path): + return _DownloadState( + torrent_id="RD1", + name="Dune", + target_dir=tmp_path, + phase="waiting_rd", + ) + + def test_queued_remains_downloading_and_allows_later_status(self, tmp_path): + client = self._client() + state = self._state(tmp_path) + + queued = client._handle_torrent_info( + {"status": "queued", "progress": 0, "filename": "Dune.epub"}, + state, + ) + + assert queued.state == DownloadState.DOWNLOADING + assert queued.progress == 0 + assert queued.complete is False + assert state.phase == "waiting_rd" + assert state.error_message is None + + downloading = client._handle_torrent_info( + {"status": "downloading", "progress": 10, "filename": "Dune.epub"}, + state, + ) + + assert downloading.state == DownloadState.DOWNLOADING + assert state.phase == "waiting_rd" + + def test_dead_is_terminal_and_caches_error(self, tmp_path): + client = self._client() + state = self._state(tmp_path) + + status = client._handle_torrent_info({"status": "dead"}, state) + + assert status.state == DownloadState.ERROR + assert state.phase == "error" + assert state.error_message == "Real-Debrid status error: dead" + + class TestAllDebridAdd: @staticmethod def _client(monkeypatch):