feat: execute durable archive transfers

This commit is contained in:
2026-07-23 07:21:19 +00:00
parent 68db3ec4c5
commit 884aed9921
14 changed files with 1618 additions and 8 deletions
+91
View File
@@ -108,6 +108,11 @@ class QBittorrentReaderTests(unittest.TestCase):
b"", # stop before recheck
b'{"total_downloaded":0}',
b"", # recheck
json.dumps([{
"hash": torrent_hash, "state": "stoppedDL",
}]).encode(),
b'{"total_downloaded":0}',
b'[{"index":0,"progress":0},{"index":1,"progress":0}]',
json.dumps([{
"hash": torrent_hash, "state": "checkingUP",
}]).encode(),
@@ -118,6 +123,7 @@ class QBittorrentReaderTests(unittest.TestCase):
}]).encode(),
b'{"total_downloaded":0}',
b'[{"index":0,"progress":1},{"index":1,"progress":0}]',
b"", # start after successful recheck
b"", # entry-only delete
]
with tempfile.TemporaryDirectory() as directory:
@@ -140,6 +146,7 @@ class QBittorrentReaderTests(unittest.TestCase):
result = adapter.recheck_and_wait(
torrent_hash, [0], timeout=1, poll_interval=0
)
adapter.start(torrent_hash)
adapter.delete_entry(torrent_hash)
self.assertEqual(result.final_state, "stoppedUP")
@@ -155,6 +162,7 @@ class QBittorrentReaderTests(unittest.TestCase):
for call in calls[2:]
if getattr(call, "data", None)
]
self.assertIn("id=0%7C1", form_bodies[0])
self.assertIn("priority=0", form_bodies[0])
self.assertIn("priority=1", form_bodies[1])
self.assertIn("deleteFiles=false", form_bodies[-1])
@@ -182,6 +190,89 @@ class QBittorrentReaderTests(unittest.TestCase):
self.assertTrue(opener.calls[-1].full_url.endswith("/torrents/pause"))
def test_start_falls_back_to_qbittorrent_4_resume_endpoint(self):
missing = error.HTTPError(
"http://qb/api/v2/torrents/start", 404, "not found", {}, None
)
responses = [b"Ok.", missing, b""]
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
password = root / "password"
password.write_text("secret", encoding="utf-8")
os.chmod(password, 0o600)
config = ServiceConfig(
"http://qb", PurePosixPath("/downloads"), root,
username="admin", password_file=password,
)
opener = _Opener(responses)
with patch(
"archive_clients.qbittorrent.request.build_opener",
return_value=opener,
):
QBittorrentReader(config).start("a" * 40)
self.assertTrue(opener.calls[-1].full_url.endswith("/torrents/resume"))
def test_wait_until_present_polls_until_added_torrent_is_visible(self):
torrent_hash = "a" * 40
responses = [
b"Ok.",
b"[]",
json.dumps([{"hash": torrent_hash}]).encode(),
]
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
password = root / "password"
password.write_text("secret", encoding="utf-8")
os.chmod(password, 0o600)
config = ServiceConfig(
"http://qb", PurePosixPath("/downloads"), root,
username="admin", password_file=password,
)
opener = _Opener(responses)
with patch(
"archive_clients.qbittorrent.request.build_opener",
return_value=opener,
):
QBittorrentReader(config).wait_until_present(
torrent_hash, timeout=1, poll_interval=0
)
self.assertEqual(len(opener.calls), 3)
def test_stopped_add_retries_while_recently_deleted_hash_is_busy(self):
torrent_hash = "a" * 40
responses = [
b"Ok.",
b"Fails.",
b"[]",
b"Ok.",
json.dumps([{"hash": torrent_hash}]).encode(),
]
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
password = root / "password"
password.write_text("secret", encoding="utf-8")
os.chmod(password, 0o600)
config = ServiceConfig(
"http://qb", PurePosixPath("/downloads"), root,
username="admin", password_file=password,
)
opener = _Opener(responses)
with patch(
"archive_clients.qbittorrent.request.build_opener",
return_value=opener,
):
QBittorrentReader(config).add_stopped_with_retry(
b"torrent",
"/downloads",
torrent_hash,
max_attempts=2,
initial_delay=0,
)
self.assertEqual(len(opener.calls), 5)
if __name__ == "__main__":
unittest.main()