mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-11-10 12:28:27 +00:00
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 -->
56 lines
1.5 KiB
Python
56 lines
1.5 KiB
Python
"""The ledger of torrents this instance added."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from shelfmark.download import torrent_ledger
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _ledger_file(tmp_path, monkeypatch):
|
|
monkeypatch.setattr(torrent_ledger, "_path", lambda: tmp_path / "torrent_ledger.db")
|
|
|
|
|
|
def test_a_recorded_hash_is_kept_lower_case_with_its_task():
|
|
torrent_ledger.record("ABCDEF", "task-1")
|
|
|
|
assert torrent_ledger.entries() == {"abcdef": "task-1"}
|
|
|
|
|
|
def test_recording_a_hash_again_moves_it_to_the_new_task():
|
|
torrent_ledger.record("abc", "task-1")
|
|
torrent_ledger.record("ABC", "task-2")
|
|
|
|
assert torrent_ledger.entries() == {"abc": "task-2"}
|
|
|
|
|
|
def test_claiming_moves_a_known_hash_and_never_adds_an_unknown_one():
|
|
torrent_ledger.record("abc", "task-1")
|
|
|
|
torrent_ledger.claim("abc", "task-2")
|
|
torrent_ledger.claim("unknown", "task-2")
|
|
|
|
assert torrent_ledger.entries() == {"abc": "task-2"}
|
|
|
|
|
|
def test_forget_drops_only_the_named_hashes():
|
|
torrent_ledger.record("a", "t1")
|
|
torrent_ledger.record("b", "t2")
|
|
|
|
torrent_ledger.forget("A")
|
|
torrent_ledger.forget()
|
|
torrent_ledger.forget("")
|
|
|
|
assert torrent_ledger.entries() == {"b": "t2"}
|
|
|
|
|
|
def test_an_unusable_ledger_never_raises(monkeypatch, tmp_path):
|
|
monkeypatch.setattr(torrent_ledger, "_path", lambda: tmp_path / "missing" / "ledger.db")
|
|
|
|
torrent_ledger.record("a", "t1")
|
|
torrent_ledger.claim("a", "t2")
|
|
torrent_ledger.forget("a")
|
|
|
|
assert torrent_ledger.entries() == {}
|