Transmission RPC client #52
Reference in New Issue
Block a user
Delete Branch "issue/18-transmission"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds the
arr-dlTransmission 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
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-e2ecovers none of it, and the e2e job only runs on PRs touching these paths. The 409 handshake, thetorrent-duplicatebranch and the status mapping are all pure request/response logic, andwiremockis already in[workspace.dependencies]under test-only.@@ -2,0 +96,4 @@pub fn new(endpoint: &str) -> Result<Self, Error> {Ok(Self {endpoint: Url::parse(endpoint)?,http: reqwest::Client::new(),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(..).@@ -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,json!expands toserde_json::to_value(..).unwrap(), andPathBuf'sSerializeerrors on a non-UTF-8 path — so a non-UTF-8 download dir panics here rather than returningError. Convert the path explicitly and returnInvalidResponse/a new variant when it is not UTF-8.@@ -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") {On
torrent-duplicateTransmission ignores the arguments — the pre-existing torrent keeps whatever label, download dir and seed limits it already had. The caller getswas_duplicate: trueand no indication that themovies-mainlabel (§7.1) and the seed obligation (§7.3) were never applied. Follow a duplicate with atorrent-setfor those fields, or document that the caller must.@@ -2,0 +277,4 @@4 => TorrentState::Downloading,5 => TorrentState::QueuedToSeed,6 => TorrentState::Seeding,status => {An unrecognised status fails the whole
list_torrentscall, so a single odd torrent makes every other torrent invisible to a client that treats Transmission as authoritative. Map unknown codes to anUnknownvariant (or drop that torrent) instead of failing the batch.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.@@ -2,0 +173,4 @@.await?;self.call("torrent-set-location",json!({"ids": [torrent.id], "location": download_dir, "move": false}),move: falsetells Transmission the data is already atlocationand 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: trueis the safe value here; alternatively skip the call when the duplicate'sdownloadDiralready matches.The
torrent-setabove it has no such problem — labels and seed limits are plain metadata.Reviewed
daadf8a. No findings.Reviewed
b26a007. No findings — merge ofmain, nothing inarr-dlorarr-e2echanged.