Files
shelfmark/tests/download/test_orchestrator_retry_persisted.py
T
splitsec2 bb13fae0bb feat(download): sweep dead torrents and Soulseek leftovers on their own timer (#1619)
This adds two cleanups that nobody notices until they matter, and puts
them on their own timer. Both do nothing until their setting and client
are configured.

The dead-torrent sweep removes a torrent Shelfmark added once it has
been stalled, or stuck fetching metadata, for `STALE_TORRENT_HOURS` with
no running download owning it, and its partial data goes with it. It
only touches torrents this instance added, tracked in a ledger of
hashes, whatever category they are in, and only on qBittorrent. The
default is 6 hours (3 to 72, and 0 turns it off). I am not sure 6 is
right for everyone, so say if you would rather ship it off.

The Soulseek sweep covers what a restart leaves in slskd's downloads
folder. A finished folder nothing imported gets imported, a partial
folder is removed after `SLSKD_CLEAN_LEFTOVERS_DAYS`, and unfinished
transfers no running download owns are picked up again when their
request still waits for them.

Both run from a small background thread every 15 minutes, after a 30
second startup delay. I first ran them from the Hardcover scheduler, but
that only runs when Hardcover sync or auto-download is on, so anyone
using neither would never get the cleanup. A separate thread keeps them
independent, and the interval is fixed so there is no new setting.

The new tests mock the sweeps. I also started a scratch instance, and
the thread starts and the first cycle is a no-op when nothing is
configured. The timer has not run against a real qBittorrent or slskd,
though the two sweeps themselves have been in my fork's builds since
October. Full Python suite, ruff and basedpyright pass.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **New Features**
* Shelfmark periodically recovers eligible complete Soulseek downloads
and reattaches unfinished transfers. It can also clean up eligible old
folders while protecting active, wanted, and already-imported downloads.
* Shelfmark can remove tracked qBittorrent torrents that have stalled or
exceeded the configured inactivity limit, while preserving torrents
associated with active tasks. Set the limit to 0 to disable cleanup.
* **Bug Fixes**
* Isolated Soulseek downloads reject task IDs that could produce unsafe
or mismatched destination folders.
* **Documentation**
* Added configuration guidance for Soulseek recovery and cleanup, and
qBittorrent stale-torrent cleanup, including defaults and eligibility
rules.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-11-09 13:20:21 -06:00

36 lines
1.2 KiB
Python

"""retry_persisted_download: what allow_request_linked lifts, and what it does not."""
from shelfmark.download import orchestrator
def _payload(**extra):
"""A minimal persisted payload for a request-linked download."""
payload = {
"task_id": "slskd:aa",
"source": "slskd",
"title": "Book",
"request_id": 7,
}
payload.update(extra)
return payload
def test_the_flag_still_refuses_a_download_with_nothing_to_restart_from():
"""allow_request_linked lifts the retry-from-requests rule, not the no-source rule."""
ok, message = orchestrator.retry_persisted_download(
_payload(can_retry_without_staged_source=False),
final_status="error",
allow_request_linked=True,
)
assert ok is False
assert message == "Download cannot be retried"
def test_without_the_flag_a_request_linked_failure_goes_back_to_its_request():
"""Without the flag, a failed request download is retried from the request."""
ok, message = orchestrator.retry_persisted_download(_payload(), final_status="error")
assert ok is False
assert message == "Request-linked downloads must be retried from requests"