From a60eae931555018f6203d408026f4bef7d31fc85 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Fri, 29 May 2026 19:29:55 -0700 Subject: [PATCH] Soulseek album poll: treat 'Aborted'/'Cancelled' transfers as failed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- core/soulseek_client.py | 9 ++++++++- tests/test_soulseek_album_poll_stall.py | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/core/soulseek_client.py b/core/soulseek_client.py index e484468f..aa624d1e 100644 --- a/core/soulseek_client.py +++ b/core/soulseek_client.py @@ -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, " (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)", diff --git a/tests/test_soulseek_album_poll_stall.py b/tests/test_soulseek_album_poll_stall.py index 41ea071a..5fc501cc 100644 --- a/tests/test_soulseek_album_poll_stall.py +++ b/tests/test_soulseek_album_poll_stall.py @@ -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")]