From a5267ee8cf124084202babae063b158ce2aa434f Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Thu, 18 Jun 2026 12:47:19 -0700 Subject: [PATCH] =?UTF-8?q?NZBGet:=20import=20from=20the=20final=20locatio?= =?UTF-8?q?n,=20not=20the=20incomplete=20'=E2=80=A6.#NZBID'=20dir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swigs: 'No audio files found in /data/usenet/incomplete/….#2141' — SoulSync imported a usenet album from NZBGet's intermediate working dir, which is emptied after the move (files were in /data/soulseek/…). Two causes, both fixed to match the already-correct SAB adapter: - _parse_history mapped save_path=DestDir, but the authoritative final location after a post-processing move is FinalDir. Prefer FinalDir, fall back to DestDir, empty/whitespace -> None. - _parse_group exposed the queue group's DestDir (the in-progress '….#NZBID' dir) as save_path, so a PP_FINISHED group (which maps to 'completed') could finalize on the incomplete folder before the move. A queue group now reports no save_path -> finalisation always comes from the history entry (real FinalDir/DestDir), bridged by the existing 120s completed-no-path window. 6 regression tests (FinalDir preferred, DestDir fallback, empty->None, queue/ PP_FINISHED never offer the incomplete path). --- core/usenet_clients/nzbget.py | 20 ++++++++- tests/test_usenet_client_adapters.py | 61 ++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/core/usenet_clients/nzbget.py b/core/usenet_clients/nzbget.py index d58074ff..b8c30dad 100644 --- a/core/usenet_clients/nzbget.py +++ b/core/usenet_clients/nzbget.py @@ -198,7 +198,15 @@ class NZBGetAdapter: size=size_bytes, downloaded=downloaded_bytes, download_speed=speed, - save_path=group.get('DestDir'), + # A QUEUED group's DestDir is the in-progress intermediate dir + # (NZBGet names it '.#' and empties/renames it once + # the move completes). Never offer it as a final save_path — finalize + # only from the HISTORY entry (real FinalDir/DestDir). Otherwise a + # PP_FINISHED group (which maps to 'completed') would finalize on the + # incomplete '....#2141' dir, which is then gone -> "No audio files + # found in /…/incomplete/….#2141" (Swigs). Mirrors the SAB adapter + # ignoring its incomplete_path. + save_path=None, category=group.get('Category'), ) @@ -217,7 +225,15 @@ class NZBGetAdapter: size=size_bytes, downloaded=size_bytes if not is_failed else 0, download_speed=0, - save_path=entry.get('DestDir'), + # Prefer FinalDir — the location after a post-processing script (or + # NZBGet's own move) relocated the files; DestDir can still point at + # the intermediate '….#NZBID' dir. Swigs: files landed in + # /data/soulseek/… (FinalDir) while DestDir stayed /…/incomplete/….#2141. + # Fall back to DestDir when FinalDir is empty (no PP move). Empty/ + # whitespace -> None so the plugin waits for a real path. + save_path=(str(entry.get('FinalDir') or '').strip() + or str(entry.get('DestDir') or '').strip() + or None), category=entry.get('Category'), error=status_field if is_failed else None, ) diff --git a/tests/test_usenet_client_adapters.py b/tests/test_usenet_client_adapters.py index 1445a36c..095d100d 100644 --- a/tests/test_usenet_client_adapters.py +++ b/tests/test_usenet_client_adapters.py @@ -714,6 +714,67 @@ def test_nzbget_parse_group_computes_progress() -> None: assert status.download_speed == 500_000 +def test_nzbget_queue_group_never_offers_a_save_path() -> None: + """A queued group's DestDir is the in-progress '….#NZBID' dir, which is gone + after the move — never expose it as a final save_path. Finalisation must come + from the HISTORY entry. (Swigs: imported from /…/incomplete/….#2141.)""" + adapter = _nzbget_with_config() + status = adapter._parse_group({ + 'NZBID': 2141, 'NZBName': 'xRepentancex-The.Sickness.Of.Eden', + 'Status': 'DOWNLOADING', + 'DestDir': '/data/usenet/incomplete/xRepentancex-The.Sickness.Of.Eden.#2141', + 'Category': 'soulsync', + }) + assert status.save_path is None + + +def test_nzbget_pp_finished_group_is_completed_but_has_no_path() -> None: + """PP_FINISHED maps to 'completed' but is still a QUEUE group on the + in-progress dir — it must report no save_path so the plugin waits for the + history entry instead of finalising on the incomplete folder.""" + adapter = _nzbget_with_config() + status = adapter._parse_group({ + 'NZBID': 2141, 'NZBName': 'Album', 'Status': 'PP_FINISHED', + 'DestDir': '/data/usenet/incomplete/Album.#2141', 'Category': 'soulsync', + }) + assert status.state == 'completed' + assert status.save_path is None + + +def test_nzbget_history_prefers_finaldir_over_destdir() -> None: + """After a post-processing move, FinalDir is the real location; DestDir can + still be the intermediate dir. Swigs' exact case.""" + adapter = _nzbget_with_config() + status = adapter._parse_history({ + 'NZBID': 2141, 'Name': 'xRepentancex-The.Sickness.Of.Eden', + 'Status': 'SUCCESS/ALL', + 'DestDir': '/data/usenet/incomplete/xRepentancex-The.Sickness.Of.Eden.#2141', + 'FinalDir': '/data/soulseek/xRepentancex-The.Sickness.Of.Eden-CD-FLAC-2015-CATARACT', + 'Category': 'soulsync', + }) + assert status.state == 'completed' + assert status.save_path == '/data/soulseek/xRepentancex-The.Sickness.Of.Eden-CD-FLAC-2015-CATARACT' + + +def test_nzbget_history_falls_back_to_destdir_when_no_finaldir() -> None: + """No PP move -> FinalDir empty -> use DestDir (the final dest in that case).""" + adapter = _nzbget_with_config() + status = adapter._parse_history({ + 'NZBID': 7, 'Name': 'Album', 'Status': 'SUCCESS/HEALTH', + 'DestDir': '/data/usenet/completed/Album', 'FinalDir': '', 'Category': 'c', + }) + assert status.save_path == '/data/usenet/completed/Album' + + +def test_nzbget_history_empty_dirs_yield_none() -> None: + adapter = _nzbget_with_config() + status = adapter._parse_history({ + 'NZBID': 7, 'Name': 'Album', 'Status': 'SUCCESS/ALL', + 'DestDir': ' ', 'FinalDir': '', 'Category': 'c', + }) + assert status.save_path is None + + def test_nzbget_remove_rejects_non_numeric_id() -> None: """NZBGet IDs are ints; passing a string id like 'abc' must fail fast instead of corrupting the editqueue call."""