NZBGet: import from the final location, not the incomplete '….#NZBID' dir

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).
This commit is contained in:
BoulderBadgeDad 2026-06-18 12:47:19 -07:00
parent 2ecbd8badc
commit a5267ee8cf
2 changed files with 79 additions and 2 deletions

View file

@ -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 '<NZBName>.#<NZBID>' 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,
)

View file

@ -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."""