Soulseek album poll: treat 'Aborted'/'Cancelled' transfers as failed

Live testing surfaced that slskd reports a peer-side abort as 'Completed,
Aborted' at 0 bytes (peer accepts then drops every transfer). That string
contains 'Completed', so the poll's completed-branch ran first and misread it as
'completed but file missing' — routing it into the #715 unresolved/download_path
grace (gives up after 45s with a misleading 'download_path mismatch' log)
instead of recognizing it as a failure.

Add 'Aborted' and 'Cancelled' to the failure-token check (which runs before the
completed branch), so these resolve immediately and correctly as failed. Test
added for the all-aborted folder.
This commit is contained in:
BoulderBadgeDad 2026-05-29 19:29:55 -07:00
parent aa2806180e
commit a60eae9315
2 changed files with 24 additions and 1 deletions

View file

@ -1740,7 +1740,14 @@ class SoulseekClient(DownloadSourcePlugin):
os.path.basename((key[1] or '').replace('\\', '/')),
))
state = (getattr(dl, 'state', '') or '') if dl else ''
if any(token in state for token in ('Errored', 'Failed', 'Rejected', 'TimedOut')):
# NOTE: check failure tokens BEFORE the 'Completed' branch — slskd
# reports terminal failures as "Completed, <reason>" (e.g.
# "Completed, Aborted" / "Completed, Cancelled" when a peer accepts
# then drops every transfer at 0 bytes). Those contain "Completed",
# so without catching the failure reason first they'd be misread as
# "completed but file missing" (the #715 download_path path).
if any(token in state for token in
('Errored', 'Failed', 'Rejected', 'TimedOut', 'Aborted', 'Cancelled')):
failed_states[key] = state or 'Failed'
logger.warning(
"[Soulseek album] Transfer failed from selected folder: %s (%s)",

View file

@ -137,6 +137,22 @@ def test_dropped_transfers_also_stall_out():
assert clock.now < 600.0
def test_completed_aborted_classified_as_failed_not_unresolved():
"""slskd reports a peer-side abort as 'Completed, Aborted' at 0 bytes. Because
that string contains 'Completed', it was misread as 'completed but file
missing' (#715 path). It must be classified as FAILED — so an all-aborted
folder resolves immediately, not after the unresolved/stall grace."""
tk = _keys("01.flac", "02.flac")
aborted = [
_dl("peer", "01.flac", "Completed, Aborted", transferred=0, size=3_600_000),
_dl("peer", "02.flac", "Completed, Aborted", transferred=0, size=24_700_000),
]
stub = _StubClient([aborted], resolvable=set())
result, clock, _ = _run_poll(stub, tk, timeout=7200.0, interval=2.0)
assert result == [] # all failed → empty (caller falls back)
assert clock.now < 30.0 # resolved on the first poll, no 45s/180s wait
def test_clean_finish_unaffected():
tk = _keys("01.flac", "02.flac")
done = [_dl("peer", "01.flac", "Completed"), _dl("peer", "02.flac", "Succeeded")]