67efdedb51
Adds the _needs_enqueue() helper (a file is (re)enqueued only if it has no
transfer yet or its last one ended Errored/Cancelled/Rejected). This is step 1
of making SlskdClient._download idempotent so a retry/hard-kill resumes instead
of re-downloading the whole album from scratch.
REMAINING (continue here):
1. Add SlskdClient._existing_states(username) -> {filename: state} (GET
/api/v0/transfers/downloads/{username}, best-effort, {} on error).
2. In _download(): before the POST, compute
existing = self._existing_states(username)
to_enqueue = [f for f in files if _needs_enqueue(existing.get(f["filename"], ""))]
POST only to_enqueue (skip the POST entirely when empty); poll loop unchanged.
Effect: a completed prior copy is retrieved immediately; a partial one resumes.
3. Tests in test_slskd_cancel.py: resume-skips-completed (no POST, immediate
retrieve) + resume-only-enqueues-missing (partial). Traced: the 3 existing
download tests still pass (the extra pre-loop GET consumes poll0 and degrades
gracefully) — but RUN the suite to confirm.
4. Merge to main, build+push worker image, deploy — but only AFTER the in-flight
Drake "Scorpion" job (soulseekboy43) has imported, so the deploy's worker
recreate doesn't interrupt it.
Deferred follow-up (bigger, needs candidate reconstruction): pipeline
peer-stickiness (reuse the persisted chosen candidate on retry instead of
re-searching) + cancel a reclaimed job's orphaned transfers at startup. That is
what fully stops the cross-peer duplicate the user saw.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>