From 401ccb69b2d2eb1bf29a8d7c3fe4e5454ca1ed2b Mon Sep 17 00:00:00 2001 From: spinline Date: Sat, 21 Feb 2026 20:00:00 +0300 Subject: [PATCH] fix(backend): Support TCP SCGI sockets and remove verbose unstandardized tracker fields --- backend/src/bin/test_trackers.rs | 83 ++++++++++++++++++++++++++++++++ shared/src/scgi.rs | 15 ++++-- shared/src/server_fns/torrent.rs | 16 +++--- test_rpc.rs | 26 ++++++++++ 4 files changed, 128 insertions(+), 12 deletions(-) create mode 100644 backend/src/bin/test_trackers.rs create mode 100644 test_rpc.rs diff --git a/backend/src/bin/test_trackers.rs b/backend/src/bin/test_trackers.rs new file mode 100644 index 0000000..87c6888 --- /dev/null +++ b/backend/src/bin/test_trackers.rs @@ -0,0 +1,83 @@ +use shared::xmlrpc::{RtorrentClient, RpcParam, parse_multicall_response}; + +#[tokio::main] +async fn main() { + let mut client = None; + for path in ["127.0.0.1:8000", "0.0.0.0:8000", "localhost:8000"] { + let test_client = RtorrentClient::new(path); + match test_client.call("system.client_version", &[]).await { + Ok(res) => { + println!("SUCCESS: Connected to rTorrent at {} (Version: {})", path, res); + client = Some(test_client); + break; + } + Err(_) => { + // println!("Failed to connect to {}", path); + } + } + } + + let client = match client { + Some(c) => c, + None => { + println!("Could not connect to rTorrent on port 8000."); + return; + } + }; + + let mut hash = String::new(); + match client.call("d.multicall2", &[RpcParam::from(""), RpcParam::from("main"), RpcParam::from("d.hash=")]).await { + Ok(xml) => { + if let Ok(rows) = parse_multicall_response(&xml) { + if let Some(row) = rows.first() { + if let Some(h) = row.first() { + hash = h.clone(); + println!("Using torrent hash: {}", hash); + } + } + } + }, + Err(e) => { + println!("Error getting torrents: {:?}", e); + return; + }, + } + + if hash.is_empty() { + println!("No torrents found to test trackers."); + return; + } + + // Now test Tracker fields one by one to see which one is failing + let fields = vec![ + "t.url=", + "t.is_enabled=", + "t.group=", + "t.scrape_complete=", + "t.scrape_incomplete=", + "t.scrape_downloaded=", + "t.activity_date_last=", + "t.normal_interval=", + "t.message=", + ]; + + for field in &fields { + let params = vec![ + RpcParam::from(hash.as_str()), + RpcParam::from(""), + RpcParam::from(*field), + ]; + + print!("Testing field {:<22} : ", field); + match client.call("t.multicall", ¶ms).await { + Ok(xml) => { + if xml.contains("faultCode") { + println!("FAILED"); + } else { + println!("SUCCESS"); + } + }, + Err(e) => println!("ERROR: {:?}", e), + } + } +} diff --git a/shared/src/scgi.rs b/shared/src/scgi.rs index eb1db03..dc5187b 100644 --- a/shared/src/scgi.rs +++ b/shared/src/scgi.rs @@ -83,12 +83,19 @@ impl ScgiRequest { pub async fn send_request(socket_path: &str, request: ScgiRequest) -> Result { let perform_request = async { - let mut stream = UnixStream::connect(socket_path).await?; let data = request.encode(); - stream.write_all(&data).await?; - let mut response = Vec::new(); - stream.read_to_end(&mut response).await?; + + if socket_path.contains(':') { + let mut stream = tokio::net::TcpStream::connect(socket_path).await?; + stream.write_all(&data).await?; + stream.read_to_end(&mut response).await?; + } else { + let mut stream = tokio::net::UnixStream::connect(socket_path).await?; + stream.write_all(&data).await?; + stream.read_to_end(&mut response).await?; + } + Ok::, std::io::Error>(response) }; diff --git a/shared/src/server_fns/torrent.rs b/shared/src/server_fns/torrent.rs index 1c6e754..c052dc9 100644 --- a/shared/src/server_fns/torrent.rs +++ b/shared/src/server_fns/torrent.rs @@ -198,9 +198,7 @@ pub async fn get_trackers(hash: String) -> Result, ServerFnE RpcParam::from("t.scrape_complete="), RpcParam::from("t.scrape_incomplete="), RpcParam::from("t.scrape_downloaded="), - RpcParam::from("t.activity_date_last="), RpcParam::from("t.normal_interval="), - RpcParam::from("t.message="), ]; let xml = client @@ -211,7 +209,7 @@ pub async fn get_trackers(hash: String) -> Result, ServerFnE let rows = parse_multicall_response(&xml) .map_err(|e| ServerFnError::new(format!("Parse error: {}", e)))?; - Ok(rows + let result: Vec = rows .into_iter() .map(|row| TorrentTracker { url: row.get(0).cloned().unwrap_or_default(), @@ -220,12 +218,14 @@ pub async fn get_trackers(hash: String) -> Result, ServerFnE seeders: row.get(3).and_then(|s| s.parse().ok()).unwrap_or(0), peers: row.get(4).and_then(|s| s.parse().ok()).unwrap_or(0), downloaded: row.get(5).and_then(|s| s.parse().ok()).unwrap_or(0), - last_updated: row.get(6).and_then(|s| s.parse().ok()).unwrap_or(0), - interval: row.get(7).and_then(|s| s.parse().ok()).unwrap_or(0), - status: "Unknown".to_string(), // Can derive from message or activity later, or keep unknown - message: row.get(8).cloned().unwrap_or_default(), + interval: row.get(6).and_then(|s| s.parse().ok()).unwrap_or(0), + last_updated: 0, + status: "Unknown".to_string(), // Can derive from activity later, or keep unknown + message: "".to_string(), }) - .collect()) + .collect(); + + Ok(result) } #[server(SetFilePriority, "/api/server_fns")] diff --git a/test_rpc.rs b/test_rpc.rs new file mode 100644 index 0000000..31a993c --- /dev/null +++ b/test_rpc.rs @@ -0,0 +1,26 @@ +use shared::xmlrpc::{RtorrentClient, RpcParam, parse_multicall_response}; + +#[tokio::main] +async fn main() { + let client = RtorrentClient::new("/tmp/rtorrent.sock"); + + // Hardcode a known hash from the UI, e.g. "C3315ABFAD70C54505813D1303C1457900C5B795" (from first image) + let hash = "C3315ABFAD70C54505813D1303C1457900C5B795"; + + let params = vec![ + RpcParam::from(hash), + RpcParam::from(""), + RpcParam::from("t.url="), + ]; + + match client.call("t.multicall", ¶ms).await { + Ok(xml) => { + println!("Response XML:\n{}", xml); + match parse_multicall_response(&xml) { + Ok(rows) => println!("Rows ({})", rows.len()), + Err(e) => println!("Parse error: {:?}", e), + } + }, + Err(e) => println!("Error: {:?}", e), + } +}