feat: execute durable archive transfers
This commit is contained in:
@@ -126,6 +126,37 @@ class QBittorrentReader:
|
||||
)
|
||||
_require_mutation_success(response, "add torrent")
|
||||
|
||||
def add_stopped_with_retry(
|
||||
self,
|
||||
metainfo: bytes,
|
||||
save_path: str,
|
||||
torrent_hash: str,
|
||||
*,
|
||||
max_attempts: int = 3,
|
||||
initial_delay: float = 1,
|
||||
) -> None:
|
||||
if max_attempts < 1 or initial_delay < 0:
|
||||
raise QBittorrentError("torrent add retry values are invalid")
|
||||
delay = initial_delay
|
||||
last_error: QBittorrentError | None = None
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
try:
|
||||
self.add_stopped(metainfo, save_path)
|
||||
self.wait_until_present(
|
||||
torrent_hash, timeout=15, poll_interval=0.1
|
||||
)
|
||||
return
|
||||
except QBittorrentError as error:
|
||||
last_error = error
|
||||
if self._torrent_is_present(torrent_hash):
|
||||
return
|
||||
if attempt == max_attempts:
|
||||
break
|
||||
time.sleep(delay)
|
||||
delay *= 2
|
||||
assert last_error is not None
|
||||
raise last_error
|
||||
|
||||
def set_selection(
|
||||
self,
|
||||
torrent_hash: str,
|
||||
@@ -141,7 +172,9 @@ class QBittorrentReader:
|
||||
"/api/v2/torrents/filePrio",
|
||||
{
|
||||
"hash": torrent_hash,
|
||||
"id": f"0-{total_file_count - 1}",
|
||||
"id": "|".join(
|
||||
str(index) for index in range(total_file_count)
|
||||
),
|
||||
"priority": "0",
|
||||
},
|
||||
)
|
||||
@@ -166,6 +199,53 @@ class QBittorrentReader:
|
||||
"/api/v2/torrents/pause", {"hashes": torrent_hash}
|
||||
)
|
||||
|
||||
def start(self, torrent_hash: str) -> None:
|
||||
try:
|
||||
self._post_form(
|
||||
"/api/v2/torrents/start", {"hashes": torrent_hash}
|
||||
)
|
||||
except QBittorrentHttpError as exc:
|
||||
if exc.status != 404:
|
||||
raise
|
||||
self._post_form(
|
||||
"/api/v2/torrents/resume", {"hashes": torrent_hash}
|
||||
)
|
||||
|
||||
def wait_until_present(
|
||||
self,
|
||||
torrent_hash: str,
|
||||
*,
|
||||
timeout: float,
|
||||
poll_interval: float = 0.1,
|
||||
) -> None:
|
||||
if timeout <= 0 or poll_interval < 0:
|
||||
raise QBittorrentError("torrent lookup timing values are invalid")
|
||||
deadline = time.monotonic() + timeout
|
||||
while True:
|
||||
if self._torrent_is_present(torrent_hash):
|
||||
return
|
||||
if time.monotonic() >= deadline:
|
||||
raise QBittorrentError(
|
||||
"qBittorrent did not expose the added torrent in time"
|
||||
)
|
||||
time.sleep(
|
||||
min(poll_interval, max(0, deadline - time.monotonic()))
|
||||
)
|
||||
|
||||
def _torrent_is_present(self, torrent_hash: str) -> bool:
|
||||
torrents = self._json(
|
||||
"/api/v2/torrents/info", {"hashes": torrent_hash}
|
||||
)
|
||||
if not isinstance(torrents, list):
|
||||
raise QBittorrentError(
|
||||
"qBittorrent lookup response is invalid"
|
||||
)
|
||||
return any(
|
||||
isinstance(item, dict)
|
||||
and str(item.get("hash", "")).lower() == torrent_hash.lower()
|
||||
for item in torrents
|
||||
)
|
||||
|
||||
def delete_entry(self, torrent_hash: str) -> None:
|
||||
self._post_form(
|
||||
"/api/v2/torrents/delete",
|
||||
@@ -430,7 +510,7 @@ class QBittorrentReader:
|
||||
|
||||
|
||||
def _is_download_state(state: str) -> bool:
|
||||
return state not in {"checkingDL"} and (
|
||||
return state not in {"checkingDL", "stoppedDL", "pausedDL"} and (
|
||||
state.endswith("DL")
|
||||
or state in {"downloading", "metaDL", "forcedMetaDL"}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user