diff --git a/shelfmark/download/clients/base_handler.py b/shelfmark/download/clients/base_handler.py index 16b98e6d..835490df 100644 --- a/shelfmark/download/clients/base_handler.py +++ b/shelfmark/download/clients/base_handler.py @@ -843,6 +843,9 @@ class ExternalClientHandler(DownloadHandler, ABC): expected_hash=request.expected_hash, seeding_time_limit=request.seeding_time_limit, ratio_limit=request.ratio_limit, + # rTorrent has no category concept, so its audiobook label + # can only be chosen from the content type (#1235). + content_type=task.content_type, ) except Exception as e: if not refresh_attempted: diff --git a/shelfmark/download/clients/rtorrent.py b/shelfmark/download/clients/rtorrent.py index 9cc77829..8f0fc3fe 100644 --- a/shelfmark/download/clients/rtorrent.py +++ b/shelfmark/download/clients/rtorrent.py @@ -11,7 +11,12 @@ from urllib.parse import urlparse from shelfmark.core.config import config from shelfmark.core.logger import setup_logger -from shelfmark.core.utils import get_hardened_xmlrpc_client +from shelfmark.core.utils import ( + get_hardened_xmlrpc_client, +) +from shelfmark.core.utils import ( + is_audiobook as check_audiobook, +) from shelfmark.download.clients import ( DownloadClient, DownloadStatus, @@ -173,7 +178,8 @@ class RTorrentClient(DownloadClient): commands = [] - is_audiobook = kwargs.get("content_type") == "audiobook" + content_type = kwargs.get("content_type") + is_audiobook = check_audiobook(content_type if isinstance(content_type, str) else None) default_label = ( self._audiobook_label if is_audiobook and self._audiobook_label else self._label ) diff --git a/tests/prowlarr/test_handler.py b/tests/prowlarr/test_handler.py index 62466b0e..48336e24 100644 --- a/tests/prowlarr/test_handler.py +++ b/tests/prowlarr/test_handler.py @@ -727,6 +727,62 @@ class TestProwlarrHandlerSeedCriteria: assert call_kwargs["ratio_limit"] == 1.25 +class TestProwlarrHandlerContentType: + """Regression tests for issue #1235 — content type must reach the client.""" + + def test_download_passes_content_type_to_client(self): + """rTorrent picks its audiobook label from content_type, not category.""" + mock_client = MagicMock() + mock_client.name = "rtorrent" + mock_client.find_existing.return_value = None + mock_client.add_download.return_value = "download_id" + + with ( + patch( + "shelfmark.release_sources.prowlarr.handler.get_release", + return_value={ + "protocol": "torrent", + "title": "Test Release", + "magnetUrl": "magnet:?xt=urn:btih:abc123", + }, + ), + patch( + "shelfmark.release_sources.prowlarr.handler.get_client", + return_value=mock_client, + ), + patch( + "shelfmark.release_sources.prowlarr.handler.remove_release", + ), + patch("shelfmark.release_sources.prowlarr.handler.config.get", return_value=True), + patch.object( + ProwlarrHandler, + "_poll_and_complete", + return_value=None, + ), + ): + handler = ProwlarrHandler() + task = DownloadTask( + task_id="content-type-pass-through", + source="prowlarr", + title="Test Audiobook", + content_type="audiobook", + ) + cancel_flag = Event() + recorder = ProgressRecorder() + + handler.download( + task=task, + cancel_flag=cancel_flag, + progress_callback=recorder.progress_callback, + status_callback=recorder.status_callback, + ) + + call_kwargs = mock_client.add_download.call_args.kwargs + assert call_kwargs["content_type"] == "audiobook" + # rTorrent gets no category, so content_type is its only audiobook signal. + assert call_kwargs["category"] is None + + class TestProwlarrHandlerExistingDownload: """Tests for handling existing downloads.""" diff --git a/tests/prowlarr/test_rtorrent_client.py b/tests/prowlarr/test_rtorrent_client.py index a35dbda4..aa9493f2 100644 --- a/tests/prowlarr/test_rtorrent_client.py +++ b/tests/prowlarr/test_rtorrent_client.py @@ -563,6 +563,36 @@ class TestRTorrentClientAudiobookLabel: assert "d.custom1.set=books" in args[2] assert "d.custom1.set=audiobooks" not in args[2] + def test_uses_audiobook_label_for_compound_content_type(self, monkeypatch): + """Issue #1235 — an exact "audiobook" match missed forms like "book (audiobook)".""" + config_values = { + "RTORRENT_URL": "http://localhost:8080/RPC2", + "RTORRENT_LABEL": "books", + "RTORRENT_AUDIOBOOK_LABEL": "audiobooks", + "RTORRENT_DOWNLOAD_DIR": "/downloads", + } + mock_rpc, mock_xmlrpc, mock_torrent_info = self._make_client(monkeypatch, config_values) + + with patch.dict("sys.modules", {"xmlrpc.client": mock_xmlrpc}): + with patch( + "shelfmark.download.clients.torrent_utils.extract_torrent_info", + return_value=mock_torrent_info, + ): + if "shelfmark.download.clients.rtorrent" in sys.modules: + del sys.modules["shelfmark.download.clients.rtorrent"] + from shelfmark.download.clients.rtorrent import RTorrentClient + + client = RTorrentClient() + client.add_download( + "magnet:?xt=urn:btih:abc123", + "Test Audiobook", + content_type="Book (Audiobook)", + ) + + args = mock_rpc.load.start.call_args[0] + assert "d.custom1.set=audiobooks" in args[2] + assert "d.custom1.set=books" not in args[2] + class TestRTorrentClientGetStatus: """Tests for RTorrentClient.get_status()."""