Transmission RPC client #52

Merged
naps62-yolo merged 6 commits from issue/18-transmission into main 2026-08-22 20:22:24 +01:00
Owner

Adds the arr-dl Transmission RPC client for magnet and metainfo adds, authoritative torrent state/progress listing, and removal with optional data deletion. Add requests set the label, download directory, seed ratio, and idle limit atomically. The 409 session-ID handshake is retried transparently.

Includes a real-container integration test covering add, list, and both removal modes.

Closes #18

Adds the `arr-dl` Transmission RPC client for magnet and metainfo adds, authoritative torrent state/progress listing, and removal with optional data deletion. Add requests set the label, download directory, seed ratio, and idle limit atomically. The 409 session-ID handshake is retried transparently. Includes a real-container integration test covering add, list, and both removal modes. Closes #18
naps62-yolo added 2 commits 2026-08-22 19:47:07 +01:00
Merge remote-tracking branch 'origin/main' into issue/18-transmission
ci / rust (pull_request) Successful in 1m36s
ci / web (pull_request) Successful in 9s
e2e / e2e (pull_request) Successful in 1m22s
f551f92292
# Conflicts:
#	Cargo.lock
naps62-yolo reviewed 2026-08-22 19:51:22 +01:00
naps62-yolo left a comment
Author
Owner

Reviewed f551f92. Findings inline, plus one that has no line to sit on.

The crate has no test that runs in the fast gate — cargo nextest run --workspace --exclude arr-e2e covers none of it, and the e2e job only runs on PRs touching these paths. The 409 handshake, the torrent-duplicate branch and the status mapping are all pure request/response logic, and wiremock is already in [workspace.dependencies] under test-only.

Reviewed `f551f92`. Findings inline, plus one that has no line to sit on. The crate has no test that runs in the fast gate — `cargo nextest run --workspace --exclude arr-e2e` covers none of it, and the e2e job only runs on PRs touching these paths. The 409 handshake, the `torrent-duplicate` branch and the status mapping are all pure request/response logic, and `wiremock` is already in `[workspace.dependencies]` under test-only. <!-- agent-meta: {"model":"claude-opus-5","session":"ae3ab8fd"} -->
@@ -2,0 +96,4 @@
pub fn new(endpoint: &str) -> Result<Self, Error> {
Ok(Self {
endpoint: Url::parse(endpoint)?,
http: reqwest::Client::new(),
Author
Owner

reqwest::Client::new() sets no timeout, so a Transmission that accepts the connection and never answers parks the caller forever. §8's reconcile loop calls this on a timer; one stuck call stalls it. Build the client with .timeout(..) and .connect_timeout(..).

`reqwest::Client::new()` sets no timeout, so a Transmission that accepts the connection and never answers parks the caller forever. §8's reconcile loop calls this on a timer; one stuck call stalls it. Build the client with `.timeout(..)` and `.connect_timeout(..)`. <!-- agent-meta: {"model":"claude-opus-5","session":"ae3ab8fd"} -->
naps62-yolo marked this conversation as resolved
@@ -2,0 +109,4 @@
/// responses.
pub async fn add_torrent(&self, request: AddTorrent) -> Result<AddedTorrent, Error> {
let mut arguments = json!({
"download-dir": request.download_dir,
Author
Owner

json! expands to serde_json::to_value(..).unwrap(), and PathBuf's Serialize errors on a non-UTF-8 path — so a non-UTF-8 download dir panics here rather than returning Error. Convert the path explicitly and return InvalidResponse/a new variant when it is not UTF-8.

`json!` expands to `serde_json::to_value(..).unwrap()`, and `PathBuf`'s `Serialize` errors on a non-UTF-8 path — so a non-UTF-8 download dir panics here rather than returning `Error`. Convert the path explicitly and return `InvalidResponse`/a new variant when it is not UTF-8. <!-- agent-meta: {"model":"claude-opus-5","session":"ae3ab8fd"} -->
naps62-yolo marked this conversation as resolved
@@ -2,0 +128,4 @@
let arguments = self.call("torrent-add", arguments).await?;
let (torrent, was_duplicate) = if let Some(value) = arguments.get("torrent-added") {
(value.clone(), false)
} else if let Some(value) = arguments.get("torrent-duplicate") {
Author
Owner

On torrent-duplicate Transmission ignores the arguments — the pre-existing torrent keeps whatever label, download dir and seed limits it already had. The caller gets was_duplicate: true and no indication that the movies-main label (§7.1) and the seed obligation (§7.3) were never applied. Follow a duplicate with a torrent-set for those fields, or document that the caller must.

On `torrent-duplicate` Transmission ignores the arguments — the pre-existing torrent keeps whatever label, download dir and seed limits it already had. The caller gets `was_duplicate: true` and no indication that the `movies-main` label (§7.1) and the seed obligation (§7.3) were never applied. Follow a duplicate with a `torrent-set` for those fields, or document that the caller must. <!-- agent-meta: {"model":"claude-opus-5","session":"ae3ab8fd"} -->
naps62-yolo marked this conversation as resolved
@@ -2,0 +277,4 @@
4 => TorrentState::Downloading,
5 => TorrentState::QueuedToSeed,
6 => TorrentState::Seeding,
status => {
Author
Owner

An unrecognised status fails the whole list_torrents call, so a single odd torrent makes every other torrent invisible to a client that treats Transmission as authoritative. Map unknown codes to an Unknown variant (or drop that torrent) instead of failing the batch.

An unrecognised status fails the whole `list_torrents` call, so a single odd torrent makes every other torrent invisible to a client that treats Transmission as authoritative. Map unknown codes to an `Unknown` variant (or drop that torrent) instead of failing the batch. <!-- agent-meta: {"model":"claude-opus-5","session":"ae3ab8fd"} -->
naps62-yolo marked this conversation as resolved
naps62-yolo added 2 commits 2026-08-22 19:54:42 +01:00
Merge remote-tracking branch 'origin/main' into issue/18-transmission
ci / rust (pull_request) Failing after 18s
ci / web (pull_request) Successful in 6s
e2e / e2e (pull_request) Successful in 1m53s
8d7974e021
naps62-yolo reviewed 2026-08-22 19:55:50 +01:00
naps62-yolo left a comment
Author
Owner

Reviewed 8d7974e. Timeouts, the unknown-status variant, the non-UTF-8 path and the fast-gate tests all look right. One problem in the new duplicate path, inline.

Reviewed `8d7974e`. Timeouts, the unknown-status variant, the non-UTF-8 path and the fast-gate tests all look right. One problem in the new duplicate path, inline. <!-- agent-meta: {"model":"claude-opus-5","session":"ae3ab8fd"} -->
@@ -2,0 +173,4 @@
.await?;
self.call(
"torrent-set-location",
json!({"ids": [torrent.id], "location": download_dir, "move": false}),
Author
Owner

move: false tells Transmission the data is already at location and to look for it there. A duplicate that is currently downloading into a different directory then points at a directory with no files in it, so it restarts the download instead of being corrected. move: true is the safe value here; alternatively skip the call when the duplicate's downloadDir already matches.

The torrent-set above it has no such problem — labels and seed limits are plain metadata.

`move: false` tells Transmission the data is already at `location` and to look for it there. A duplicate that is currently downloading into a different directory then points at a directory with no files in it, so it restarts the download instead of being corrected. `move: true` is the safe value here; alternatively skip the call when the duplicate's `downloadDir` already matches. The `torrent-set` above it has no such problem — labels and seed limits are plain metadata. <!-- agent-meta: {"model":"claude-opus-5","session":"ae3ab8fd"} -->
naps62-yolo marked this conversation as resolved
naps62-yolo added 1 commit 2026-08-22 19:56:57 +01:00
fix(dl): move duplicate torrent data
ci / rust (pull_request) Successful in 1m9s
ci / web (pull_request) Successful in 6s
e2e / e2e (pull_request) Successful in 1m31s
daadf8abf3
naps62-yolo reviewed 2026-08-22 19:57:37 +01:00
naps62-yolo left a comment
Author
Owner

Reviewed daadf8a. No findings.

Reviewed `daadf8a`. No findings. <!-- agent-meta: {"model":"claude-opus-5","session":"ae3ab8fd"} -->
naps62-yolo added 1 commit 2026-08-22 20:10:18 +01:00
Merge origin/main
ci / web (pull_request) Successful in 9s
ci / rust (pull_request) Successful in 1m19s
e2e / e2e (pull_request) Successful in 1m10s
b26a007f79
naps62-yolo reviewed 2026-08-22 20:10:46 +01:00
naps62-yolo left a comment
Author
Owner

Reviewed b26a007. No findings — merge of main, nothing in arr-dl or arr-e2e changed.

Reviewed `b26a007`. No findings — merge of `main`, nothing in `arr-dl` or `arr-e2e` changed. <!-- agent-meta: {"model":"claude-opus-5","session":"ae3ab8fd"} -->
naps62-yolo merged commit 6a8051c70f into main 2026-08-22 20:22:24 +01:00
naps62-yolo deleted branch issue/18-transmission 2026-08-22 20:22:24 +01:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yolo/arr#52