feat: SharableTransClient

This commit is contained in:
George Miao 2022-10-19 13:58:32 -04:00 committed by Aleksandr
parent 7d9b03e3f5
commit 1081f5b1d9
2 changed files with 1058 additions and 91 deletions

View File

@ -1,14 +1,13 @@
extern crate env_logger;
#[macro_use]
extern crate log;
extern crate reqwest;
use reqwest::{header::CONTENT_TYPE, Url};
use reqwest::{Client, StatusCode};
use reqwest::{header::CONTENT_TYPE, Client, StatusCode, Url};
use serde::de::DeserializeOwned;
mod sync;
pub mod types;
pub use sync::SharableTransClient;
use types::{
BasicAuth, BlocklistUpdate, FreeSpace, Id, Nothing, PortTest, Result, RpcRequest, RpcResponse,
RpcResponseArgument, SessionClose, SessionGet, SessionStats, Torrent, TorrentAction,
@ -89,21 +88,27 @@ impl TransClient {
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::TransClient;
/// use transmission_rpc::types::{Result, RpcResponse, SessionGet, BasicAuth};
/// use transmission_rpc::{
/// types::{BasicAuth, Result, RpcResponse, SessionGet},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let response: Result<RpcResponse<SessionGet>> = client.session_get().await;
/// match response {
/// Ok(_) => println!("Yay!"),
/// Err(_) => panic!("Oh no!")
/// Err(_) => panic!("Oh no!"),
/// }
/// println!("Rpc response is ok: {}", response?.is_ok());
/// Ok(())
@ -125,21 +130,27 @@ impl TransClient {
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::TransClient;
/// use transmission_rpc::types::{Result, RpcResponse, SessionStats, BasicAuth};
/// use transmission_rpc::{
/// types::{BasicAuth, Result, RpcResponse, SessionStats},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let response: Result<RpcResponse<SessionStats>> = client.session_stats().await;
/// match response {
/// Ok(_) => println!("Yay!"),
/// Err(_) => panic!("Oh no!")
/// Err(_) => panic!("Oh no!"),
/// }
/// println!("Rpc response is ok: {}", response?.is_ok());
/// Ok(())
@ -161,21 +172,27 @@ impl TransClient {
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::TransClient;
/// use transmission_rpc::types::{Result, RpcResponse, BasicAuth, SessionClose};
/// use transmission_rpc::{
/// types::{BasicAuth, Result, RpcResponse, SessionClose},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let response: Result<RpcResponse<SessionClose>> = client.session_close().await;
/// match response {
/// Ok(_) => println!("Yay!"),
/// Err(_) => panic!("Oh no!")
/// Err(_) => panic!("Oh no!"),
/// }
/// println!("Rpc response is ok: {}", response?.is_ok());
/// Ok(())
@ -197,21 +214,27 @@ impl TransClient {
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::TransClient;
/// use transmission_rpc::types::{Result, BlocklistUpdate, RpcResponse, BasicAuth};
/// use transmission_rpc::{
/// types::{BasicAuth, BlocklistUpdate, Result, RpcResponse},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let response: Result<RpcResponse<BlocklistUpdate>> = client.blocklist_update().await;
/// match response {
/// Ok(_) => println!("Yay!"),
/// Err(_) => panic!("Oh no!")
/// Err(_) => panic!("Oh no!"),
/// }
/// println!("Rpc response is ok: {}", response?.is_ok());
/// Ok(())
@ -233,9 +256,12 @@ impl TransClient {
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::TransClient;
/// use transmission_rpc::types::{Result, RpcResponse, BasicAuth, FreeSpace};
/// use transmission_rpc::{
/// types::{BasicAuth, FreeSpace, Result, RpcResponse},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
@ -243,12 +269,15 @@ impl TransClient {
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let dir = env::var("TDIR")?;
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let response: Result<RpcResponse<FreeSpace>> = client.free_space(dir).await;
/// match response {
/// Ok(_) => println!("Yay!"),
/// Err(_) => panic!("Oh no!")
/// Err(_) => panic!("Oh no!"),
/// }
/// println!("Rpc response is ok: {}", response?.is_ok());
/// Ok(())
@ -270,21 +299,27 @@ impl TransClient {
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::TransClient;
/// use transmission_rpc::types::{Result, RpcResponse, BasicAuth, PortTest};
/// use transmission_rpc::{
/// types::{BasicAuth, PortTest, Result, RpcResponse},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let response: Result<RpcResponse<PortTest>> = client.port_test().await;
/// match response {
/// Ok(_) => println!("Yay!"),
/// Err(_) => panic!("Oh no!")
/// Err(_) => panic!("Oh no!"),
/// }
/// println!("Rpc response is ok: {}", response?.is_ok());
/// Ok(())
@ -308,37 +343,78 @@ impl TransClient {
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::TransClient;
/// use transmission_rpc::types::{Result, RpcResponse, BasicAuth};
/// use transmission_rpc::types::{Torrents, Torrent, TorrentGetField, Id};
/// use transmission_rpc::{
/// types::{BasicAuth, Id, Result, RpcResponse, Torrent, TorrentGetField, Torrents},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
///
/// let res: RpcResponse<Torrents<Torrent>> = client.torrent_get(None, None).await?;
/// let names: Vec<&String> = res.arguments.torrents.iter().map(|it| it.name.as_ref().unwrap()).collect();
/// let names: Vec<&String> = res
/// .arguments
/// .torrents
/// .iter()
/// .map(|it| it.name.as_ref().unwrap())
/// .collect();
/// println!("{:#?}", names);
///
/// let res1: RpcResponse<Torrents<Torrent>> = client.torrent_get(Some(vec![TorrentGetField::Id, TorrentGetField::Name]), Some(vec![Id::Id(1), Id::Id(2), Id::Id(3)])).await?;
/// let first_three: Vec<String> = res1.arguments.torrents.iter().map(|it|
/// format!("{}. {}",&it.id.as_ref().unwrap(), &it.name.as_ref().unwrap())
/// ).collect();
/// let res1: RpcResponse<Torrents<Torrent>> = client
/// .torrent_get(
/// Some(vec![TorrentGetField::Id, TorrentGetField::Name]),
/// Some(vec![Id::Id(1), Id::Id(2), Id::Id(3)]),
/// )
/// .await?;
/// let first_three: Vec<String> = res1
/// .arguments
/// .torrents
/// .iter()
/// .map(|it| {
/// format!(
/// "{}. {}",
/// &it.id.as_ref().unwrap(),
/// &it.name.as_ref().unwrap()
/// )
/// })
/// .collect();
/// println!("{:#?}", first_three);
///
///
/// let res2: RpcResponse<Torrents<Torrent>> = client.torrent_get(Some(vec![TorrentGetField::Id, TorrentGetField::HashString, TorrentGetField::Name]), Some(vec![Id::Hash(String::from("64b0d9a53ac9cd1002dad1e15522feddb00152fe"))])).await?;
/// let info: Vec<String> = res2.arguments.torrents.iter().map(|it|
/// format!("{:5}. {:^45} {}",
/// let res2: RpcResponse<Torrents<Torrent>> = client
/// .torrent_get(
/// Some(vec![
/// TorrentGetField::Id,
/// TorrentGetField::HashString,
/// TorrentGetField::Name,
/// ]),
/// Some(vec![Id::Hash(String::from(
/// "64b0d9a53ac9cd1002dad1e15522feddb00152fe",
/// ))]),
/// )
/// .await?;
/// let info: Vec<String> = res2
/// .arguments
/// .torrents
/// .iter()
/// .map(|it| {
/// format!(
/// "{:5}. {:^45} {}",
/// &it.id.as_ref().unwrap(),
/// &it.hash_string.as_ref().unwrap(),
/// &it.name.as_ref().unwrap())
/// ).collect();
/// &it.name.as_ref().unwrap()
/// )
/// })
/// .collect();
/// println!("{:#?}", info);
///
/// Ok(())
@ -366,10 +442,12 @@ impl TransClient {
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::TransClient;
/// use transmission_rpc::types::{Result, RpcResponse, BasicAuth};
/// use transmission_rpc::types::{Torrents, Torrent, TorrentSetArgs, Id};
/// use transmission_rpc::{
/// types::{BasicAuth, Id, Result, RpcResponse, Torrent, TorrentSetArgs, Torrents},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
@ -377,14 +455,22 @@ impl TransClient {
/// env_logger::init();
///
/// let url = env::var("TURL")?.parse()?;
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url, basic_auth);
///
/// let args = TorrentSetArgs {
/// labels: Some(vec![String::from("blue")]),
/// ..Default::default()
/// };
/// assert!(client.torrent_set(args, Some(vec![Id::Id(0)])).await?.is_ok());
/// assert!(
/// client
/// .torrent_set(args, Some(vec![Id::Id(0)]))
/// .await?
/// .is_ok()
/// );
///
/// Ok(())
/// }
@ -409,21 +495,30 @@ impl TransClient {
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::TransClient;
/// use transmission_rpc::types::{Result, RpcResponse, BasicAuth};
/// use transmission_rpc::types::{TorrentAction, Nothing, Id};
/// use transmission_rpc::{
/// types::{BasicAuth, Id, Nothing, Result, RpcResponse, TorrentAction},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let res1: RpcResponse<Nothing> = client.torrent_action(TorrentAction::Start, vec![Id::Id(1)]).await?;
/// let res1: RpcResponse<Nothing> = client
/// .torrent_action(TorrentAction::Start, vec![Id::Id(1)])
/// .await?;
/// println!("Start result: {:?}", &res1.is_ok());
/// let res2: RpcResponse<Nothing> = client.torrent_action(TorrentAction::Stop, vec![Id::Id(1)]).await?;
/// let res2: RpcResponse<Nothing> = client
/// .torrent_action(TorrentAction::Stop, vec![Id::Id(1)])
/// .await?;
/// println!("Stop result: {:?}", &res2.is_ok());
///
/// Ok(())
@ -449,17 +544,22 @@ impl TransClient {
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::TransClient;
/// use transmission_rpc::types::{Result, RpcResponse, BasicAuth};
/// use transmission_rpc::types::{Nothing, Id};
/// use transmission_rpc::{
/// types::{BasicAuth, Id, Nothing, Result, RpcResponse},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let res: RpcResponse<Nothing> = client.torrent_remove(vec![Id::Id(1)], false).await?;
/// println!("Remove result: {:?}", &res.is_ok());
@ -488,19 +588,30 @@ impl TransClient {
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::TransClient;
/// use transmission_rpc::types::{Result, RpcResponse, BasicAuth};
/// use transmission_rpc::types::{Nothing, Id};
/// use transmission_rpc::{
/// types::{BasicAuth, Id, Nothing, Result, RpcResponse},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let res: RpcResponse<Nothing> = client.torrent_set_location(vec![Id::Id(1)], String::from("/new/location"), Option::from(false)).await?;
/// let res: RpcResponse<Nothing> = client
/// .torrent_set_location(
/// vec![Id::Id(1)],
/// String::from("/new/location"),
/// Option::from(false),
/// )
/// .await?;
/// println!("Set-location result: {:?}", &res.is_ok());
///
/// Ok(())
@ -528,19 +639,30 @@ impl TransClient {
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::TransClient;
/// use transmission_rpc::types::{Result, RpcResponse, BasicAuth};
/// use transmission_rpc::types::{TorrentRenamePath, Id};
/// use transmission_rpc::{
/// types::{BasicAuth, Id, Result, RpcResponse, TorrentRenamePath},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let res: RpcResponse<TorrentRenamePath> = client.torrent_rename_path(vec![Id::Id(1)], String::from("Folder/OldFile.jpg"), String::from("NewFile.jpg")).await?;
/// let res: RpcResponse<TorrentRenamePath> = client
/// .torrent_rename_path(
/// vec![Id::Id(1)],
/// String::from("Folder/OldFile.jpg"),
/// String::from("NewFile.jpg"),
/// )
/// .await?;
/// println!("rename-path result: {:#?}", res);
///
/// Ok(())
@ -568,20 +690,28 @@ impl TransClient {
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::TransClient;
/// use transmission_rpc::types::{Result, RpcResponse, BasicAuth};
/// use transmission_rpc::types::{TorrentAddArgs, TorrentAddedOrDuplicate};
/// use transmission_rpc::{
/// types::{BasicAuth, Result, RpcResponse, TorrentAddArgs, TorrentAddedOrDuplicate},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth{user: env::var("TUSER")?, password: env::var("TPWD")?};
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let add: TorrentAddArgs = TorrentAddArgs {
/// filename: Some("https://releases.ubuntu.com/jammy/ubuntu-22.04.1-desktop-amd64.iso.torrent".to_string()),
/// filename: Some(
/// "https://releases.ubuntu.com/jammy/ubuntu-22.04.1-desktop-amd64.iso.torrent"
/// .to_string(),
/// ),
/// ..TorrentAddArgs::default()
/// };
/// let res: RpcResponse<TorrentAddedOrDuplicate> = client.torrent_add(add).await?;
@ -594,7 +724,6 @@ impl TransClient {
///
/// # Panics
/// Either metainfo or torrent filename must be set or this call will panic.
///
pub async fn torrent_add(
&mut self,
add: TorrentAddArgs,
@ -669,10 +798,12 @@ impl BodyString for reqwest::RequestBuilder {
#[cfg(test)]
mod tests {
use super::*;
use dotenvy::dotenv;
use std::env;
use dotenvy::dotenv;
use super::*;
#[tokio::test]
pub async fn test_malformed_url() -> Result<()> {
dotenv().ok();

836
src/sync.rs Normal file
View File

@ -0,0 +1,836 @@
use std::{ops::Deref, sync::RwLock};
use reqwest::{header::CONTENT_TYPE, Client, StatusCode, Url};
use serde::de::DeserializeOwned;
use crate::{
types::{
BasicAuth, BlocklistUpdate, FreeSpace, Id, Nothing, PortTest, Result, RpcRequest,
RpcResponse, RpcResponseArgument, SessionClose, SessionGet, SessionStats, Torrent,
TorrentAction, TorrentAddArgs, TorrentAddedOrDuplicate, TorrentGetField, TorrentRenamePath,
TorrentSetArgs, Torrents,
},
MAX_RETRIES,
};
#[derive(Clone, Debug)]
enum TransError {
MaxRetriesReached,
NoSessionIdReceived,
}
impl std::fmt::Display for TransError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
TransError::MaxRetriesReached => write!(f, "Max retries reached!"),
TransError::NoSessionIdReceived => write!(f, "No session id received!"),
}
}
}
impl std::error::Error for TransError {}
pub struct SharableTransClient {
url: Url,
auth: Option<BasicAuth>,
session_id: RwLock<Option<String>>,
client: Client,
}
impl SharableTransClient {
/// Returns HTTP(S) client with configured Basic Auth
#[must_use]
pub fn with_auth(url: Url, basic_auth: BasicAuth) -> SharableTransClient {
SharableTransClient {
url,
auth: Some(basic_auth),
session_id: RwLock::new(None),
client: Client::new(),
}
}
/// Returns HTTP(S) client
#[must_use]
pub fn new(url: Url) -> SharableTransClient {
SharableTransClient {
url,
auth: None,
session_id: RwLock::new(None),
client: Client::new(),
}
}
/// Prepares a request for provided server and auth
fn rpc_request(&self) -> reqwest::RequestBuilder {
if let Some(auth) = &self.auth {
self.client
.post(self.url.clone())
.basic_auth(&auth.user, Some(&auth.password))
} else {
self.client.post(self.url.clone())
}
.header(CONTENT_TYPE, "application/json")
}
/// Performs a session get call
///
/// # Errors
///
/// Any IO Error or Deserialization error
///
/// # Example
///
/// ```
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::{
/// types::{BasicAuth, Result, RpcResponse, SessionGet},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let response: Result<RpcResponse<SessionGet>> = client.session_get().await;
/// match response {
/// Ok(_) => println!("Yay!"),
/// Err(_) => panic!("Oh no!"),
/// }
/// println!("Rpc response is ok: {}", response?.is_ok());
/// Ok(())
/// }
/// ```
pub async fn session_get(&mut self) -> Result<RpcResponse<SessionGet>> {
self.call(RpcRequest::session_get()).await
}
/// Performs a session stats call
///
/// # Errors
///
/// Any IO Error or Deserialization error
///
/// # Example
///
/// ```
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::{
/// types::{BasicAuth, Result, RpcResponse, SessionStats},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let response: Result<RpcResponse<SessionStats>> = client.session_stats().await;
/// match response {
/// Ok(_) => println!("Yay!"),
/// Err(_) => panic!("Oh no!"),
/// }
/// println!("Rpc response is ok: {}", response?.is_ok());
/// Ok(())
/// }
/// ```
pub async fn session_stats(&mut self) -> Result<RpcResponse<SessionStats>> {
self.call(RpcRequest::session_stats()).await
}
/// Performs a session close call
///
/// # Errors
///
/// Any IO Error or Deserialization error
///
/// # Example
///
/// ```
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::{
/// types::{BasicAuth, Result, RpcResponse, SessionClose},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let response: Result<RpcResponse<SessionClose>> = client.session_close().await;
/// match response {
/// Ok(_) => println!("Yay!"),
/// Err(_) => panic!("Oh no!"),
/// }
/// println!("Rpc response is ok: {}", response?.is_ok());
/// Ok(())
/// }
/// ```
pub async fn session_close(&mut self) -> Result<RpcResponse<SessionClose>> {
self.call(RpcRequest::session_close()).await
}
/// Performs a blocklist update call
///
/// # Errors
///
/// Any IO Error or Deserialization error
///
/// # Example
///
/// ```
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::{
/// types::{BasicAuth, BlocklistUpdate, Result, RpcResponse},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let response: Result<RpcResponse<BlocklistUpdate>> = client.blocklist_update().await;
/// match response {
/// Ok(_) => println!("Yay!"),
/// Err(_) => panic!("Oh no!"),
/// }
/// println!("Rpc response is ok: {}", response?.is_ok());
/// Ok(())
/// }
/// ```
pub async fn blocklist_update(&mut self) -> Result<RpcResponse<BlocklistUpdate>> {
self.call(RpcRequest::blocklist_update()).await
}
/// Performs a session stats call
///
/// # Errors
///
/// Any IO Error or Deserialization error
///
/// # Example
///
/// ```
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::{
/// types::{BasicAuth, FreeSpace, Result, RpcResponse},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let dir = env::var("TDIR")?;
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let response: Result<RpcResponse<FreeSpace>> = client.free_space(dir).await;
/// match response {
/// Ok(_) => println!("Yay!"),
/// Err(_) => panic!("Oh no!"),
/// }
/// println!("Rpc response is ok: {}", response?.is_ok());
/// Ok(())
/// }
/// ```
pub async fn free_space(&self, path: String) -> Result<RpcResponse<FreeSpace>> {
self.call(RpcRequest::free_space(path)).await
}
/// Performs a port test call
///
/// # Errors
///
/// Any IO Error or Deserialization error
///
/// # Example
///
/// ```
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::{
/// types::{BasicAuth, PortTest, Result, RpcResponse},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let response: Result<RpcResponse<PortTest>> = client.port_test().await;
/// match response {
/// Ok(_) => println!("Yay!"),
/// Err(_) => panic!("Oh no!"),
/// }
/// println!("Rpc response is ok: {}", response?.is_ok());
/// Ok(())
/// }
/// ```
pub async fn port_test(&mut self) -> Result<RpcResponse<PortTest>> {
self.call(RpcRequest::port_test()).await
}
/// Performs a torrent get call
/// fields - if None then ALL fields
/// ids - if None then All items
///
/// # Errors
///
/// Any IO Error or Deserialization error
///
/// # Example
///
/// ```
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::{
/// types::{BasicAuth, Id, Result, RpcResponse, Torrent, TorrentGetField, Torrents},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
///
/// let res: RpcResponse<Torrents<Torrent>> = client.torrent_get(None, None).await?;
/// let names: Vec<&String> = res
/// .arguments
/// .torrents
/// .iter()
/// .map(|it| it.name.as_ref().unwrap())
/// .collect();
/// println!("{:#?}", names);
///
/// let res1: RpcResponse<Torrents<Torrent>> = client
/// .torrent_get(
/// Some(vec![TorrentGetField::Id, TorrentGetField::Name]),
/// Some(vec![Id::Id(1), Id::Id(2), Id::Id(3)]),
/// )
/// .await?;
/// let first_three: Vec<String> = res1
/// .arguments
/// .torrents
/// .iter()
/// .map(|it| {
/// format!(
/// "{}. {}",
/// &it.id.as_ref().unwrap(),
/// &it.name.as_ref().unwrap()
/// )
/// })
/// .collect();
/// println!("{:#?}", first_three);
///
/// let res2: RpcResponse<Torrents<Torrent>> = client
/// .torrent_get(
/// Some(vec![
/// TorrentGetField::Id,
/// TorrentGetField::HashString,
/// TorrentGetField::Name,
/// ]),
/// Some(vec![Id::Hash(String::from(
/// "64b0d9a53ac9cd1002dad1e15522feddb00152fe",
/// ))]),
/// )
/// .await?;
/// let info: Vec<String> = res2
/// .arguments
/// .torrents
/// .iter()
/// .map(|it| {
/// format!(
/// "{:5}. {:^45} {}",
/// &it.id.as_ref().unwrap(),
/// &it.hash_string.as_ref().unwrap(),
/// &it.name.as_ref().unwrap()
/// )
/// })
/// .collect();
/// println!("{:#?}", info);
///
/// Ok(())
/// }
/// ```
pub async fn torrent_get(
&self,
fields: Option<Vec<TorrentGetField>>,
ids: Option<Vec<Id>>,
) -> Result<RpcResponse<Torrents<Torrent>>> {
self.call(RpcRequest::torrent_get(fields, ids)).await
}
/// Performs a torrent set call
/// args - the fields to update
/// ids - if None then All items
///
/// # Errors
///
/// Any IO Error or Deserialization error
///
/// # Example
///
/// ```
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::{
/// types::{BasicAuth, Id, Result, RpcResponse, Torrent, TorrentSetArgs, Torrents},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
///
/// let url = env::var("TURL")?.parse()?;
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url, basic_auth);
///
/// let args = TorrentSetArgs {
/// labels: Some(vec![String::from("blue")]),
/// ..Default::default()
/// };
/// assert!(
/// client
/// .torrent_set(args, Some(vec![Id::Id(0)]))
/// .await?
/// .is_ok()
/// );
///
/// Ok(())
/// }
/// ```
pub async fn torrent_set(
&self,
args: TorrentSetArgs,
ids: Option<Vec<Id>>,
) -> Result<RpcResponse<Nothing>> {
self.call(RpcRequest::torrent_set(args, ids)).await
}
/// Performs a torrent action call
///
/// # Errors
///
/// Any IO Error or Deserialization error
///
/// # Example
///
/// ```
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::{
/// types::{BasicAuth, Id, Nothing, Result, RpcResponse, TorrentAction},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let res1: RpcResponse<Nothing> = client
/// .torrent_action(TorrentAction::Start, vec![Id::Id(1)])
/// .await?;
/// println!("Start result: {:?}", &res1.is_ok());
/// let res2: RpcResponse<Nothing> = client
/// .torrent_action(TorrentAction::Stop, vec![Id::Id(1)])
/// .await?;
/// println!("Stop result: {:?}", &res2.is_ok());
///
/// Ok(())
/// }
/// ```
pub async fn torrent_action(
&self,
action: TorrentAction,
ids: Vec<Id>,
) -> Result<RpcResponse<Nothing>> {
self.call(RpcRequest::torrent_action(action, ids)).await
}
/// Performs a torrent remove call
///
/// # Errors
///
/// Any IO Error or Deserialization error
///
/// # Example
///
/// ```
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::{
/// types::{BasicAuth, Id, Nothing, Result, RpcResponse},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let res: RpcResponse<Nothing> = client.torrent_remove(vec![Id::Id(1)], false).await?;
/// println!("Remove result: {:?}", &res.is_ok());
///
/// Ok(())
/// }
/// ```
pub async fn torrent_remove(
&self,
ids: Vec<Id>,
delete_local_data: bool,
) -> Result<RpcResponse<Nothing>> {
self.call(RpcRequest::torrent_remove(ids, delete_local_data))
.await
}
/// Performs a torrent set location call
///
/// # Errors
///
/// Any IO Error or Deserialization error
///
/// # Example
///
/// ```
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::{
/// types::{BasicAuth, Id, Nothing, Result, RpcResponse},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let res: RpcResponse<Nothing> = client
/// .torrent_set_location(
/// vec![Id::Id(1)],
/// String::from("/new/location"),
/// Option::from(false),
/// )
/// .await?;
/// println!("Set-location result: {:?}", &res.is_ok());
///
/// Ok(())
/// }
/// ```
pub async fn torrent_set_location(
&self,
ids: Vec<Id>,
location: String,
move_from: Option<bool>,
) -> Result<RpcResponse<Nothing>> {
self.call(RpcRequest::torrent_set_location(ids, location, move_from))
.await
}
/// Performs a torrent rename path call
///
/// # Errors
///
/// Any IO Error or Deserialization error
///
/// # Example
///
/// ```
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::{
/// types::{BasicAuth, Id, Result, RpcResponse, TorrentRenamePath},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let res: RpcResponse<TorrentRenamePath> = client
/// .torrent_rename_path(
/// vec![Id::Id(1)],
/// String::from("Folder/OldFile.jpg"),
/// String::from("NewFile.jpg"),
/// )
/// .await?;
/// println!("rename-path result: {:#?}", res);
///
/// Ok(())
/// }
/// ```
pub async fn torrent_rename_path(
&self,
ids: Vec<Id>,
path: String,
name: String,
) -> Result<RpcResponse<TorrentRenamePath>> {
self.call(RpcRequest::torrent_rename_path(ids, path, name))
.await
}
/// Performs a torrent add call
///
/// # Errors
///
/// Any IO Error or Deserialization error
///
/// # Example
///
/// ```
/// extern crate transmission_rpc;
///
/// use std::env;
///
/// use dotenvy::dotenv;
/// use transmission_rpc::{
/// types::{BasicAuth, Result, RpcResponse, TorrentAddArgs, TorrentAddedOrDuplicate},
/// TransClient,
/// };
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// dotenv().ok();
/// env_logger::init();
/// let url = env::var("TURL")?;
/// let basic_auth = BasicAuth {
/// user: env::var("TUSER")?,
/// password: env::var("TPWD")?,
/// };
/// let mut client = TransClient::with_auth(url.parse()?, basic_auth);
/// let add: TorrentAddArgs = TorrentAddArgs {
/// filename: Some(
/// "https://releases.ubuntu.com/jammy/ubuntu-22.04.1-desktop-amd64.iso.torrent"
/// .to_string(),
/// ),
/// ..TorrentAddArgs::default()
/// };
/// let res: RpcResponse<TorrentAddedOrDuplicate> = client.torrent_add(add).await?;
/// println!("Add result: {:?}", &res.is_ok());
/// println!("response: {:?}", &res);
///
/// Ok(())
/// }
/// ```
///
/// # Panics
/// Either metainfo or torrent filename must be set or this call will panic.
pub async fn torrent_add(
&self,
add: TorrentAddArgs,
) -> Result<RpcResponse<TorrentAddedOrDuplicate>> {
assert!(
!(add.metainfo.is_none() && add.filename.is_none()),
"Metainfo or Filename should be provided"
);
self.call(RpcRequest::torrent_add(add)).await
}
/// Performs a JRPC call to the server
///
/// # Errors
///
/// Any IO Error or Deserialization error
async fn call<RS>(&self, request: RpcRequest) -> Result<RpcResponse<RS>>
where
RS: RpcResponseArgument + DeserializeOwned + std::fmt::Debug,
{
let mut remaining_retries = MAX_RETRIES;
loop {
remaining_retries = remaining_retries
.checked_sub(1)
.ok_or(TransError::MaxRetriesReached)?;
info!("Loaded auth: {:?}", &self.auth);
let rq = match &self.session_id.read().expect("lock being poisoned").deref() {
None => self.rpc_request(),
Some(id) => self.rpc_request().header("X-Transmission-Session-Id", id),
}
.json(&request);
info!(
"Request body: {:?}",
rq.try_clone()
.expect("Unable to get the request body")
.body_string()?
);
let rsp: reqwest::Response = rq.send().await?;
if matches!(rsp.status(), StatusCode::CONFLICT) {
let session_id = rsp
.headers()
.get("X-Transmission-Session-Id")
.ok_or(TransError::NoSessionIdReceived)?
.to_str()?;
*self.session_id.write().expect("lock being poisoned") =
Some(String::from(session_id));
info!("Got new session_id: {}. Retrying request.", session_id);
} else {
let rpc_response: RpcResponse<RS> = rsp.json().await?;
info!("Response body: {:#?}", rpc_response);
return Ok(rpc_response);
}
}
}
}
trait BodyString {
fn body_string(self) -> Result<String>;
}
impl BodyString for reqwest::RequestBuilder {
fn body_string(self) -> Result<String> {
let rq = self.build()?;
let body = rq.body().unwrap().as_bytes().unwrap();
Ok(std::str::from_utf8(body)?.to_string())
}
}
#[cfg(test)]
mod tests {
use std::env;
use dotenvy::dotenv;
use super::*;
#[tokio::test]
pub async fn test_malformed_url() -> Result<()> {
dotenv().ok();
env_logger::init();
let url = env::var("TURL")?;
let mut client;
if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) {
client = SharableTransClient::with_auth(url.parse()?, BasicAuth { user, password });
} else {
client = SharableTransClient::new(url.parse()?);
}
info!("Client is ready!");
let add: TorrentAddArgs = TorrentAddArgs {
filename: Some(
"https://releases.ubuntu.com/jammy/ubuntu-22.04.1-desktop-amd64.iso.torrentt"
.to_string(),
),
..TorrentAddArgs::default()
};
match client.torrent_add(add).await {
Ok(res) => {
println!("Add result: {:?}", &res.is_ok());
println!("response: {:?}", &res);
assert!(!&res.is_ok());
}
Err(e) => {
println!("Error: {:#?}", e);
}
}
Ok(())
}
}