Edition 2021, rustfmt, clippy, better CI

This commit is contained in:
Artem Vorotnikov 2022-09-07 00:58:00 +03:00 committed by Aleksandr
parent e71bc1c665
commit fe596672ea
18 changed files with 200 additions and 183 deletions

View File

@ -13,41 +13,49 @@ jobs:
steps: steps:
- name: Checkout source - name: Checkout source
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Get Rust - name: Get Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
profile: minimal
toolchain: stable toolchain: stable
components: rustfmt, clippy
- name: Setup Test Enviroment - name: Setup Test Enviroment
run: docker-compose up -d run: docker-compose up -d
- name: Use Cache - name: Check formatting
uses: actions/cache@v2
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Cargo Build
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: build command: fmt
args: --release --all-features args: --all -- --check
- name: Cargo check
uses: actions-rs/cargo@v1
with:
command: check
args: --workspace --all-targets --all-features
- name: Cargo Test - name: Cargo Test
uses: actions-rs/toolchain@v1 uses: actions-rs/cargo@v1
with: with:
toolchain: stable command: test
- run: cargo test -- --skip session_close args: -- --skip session_close
- name: Cargo Test Session Close - name: Cargo Test Session Close
uses: actions-rs/toolchain@v1 uses: actions-rs/cargo@v1
with: with:
toolchain: stable command: test
- run: cargo test -- session_close args: -- session_close
- name: Cargo clippy
uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Cargo audit
uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -2,20 +2,20 @@
name = "transmission-rpc" name = "transmission-rpc"
version = "0.3.6" version = "0.3.6"
authors = ["red <red.avtovo@gmail.com>"] authors = ["red <red.avtovo@gmail.com>"]
edition = "2018" edition = "2021"
repository = "https://github.com/j0rsa/transmission-rpc" repository = "https://github.com/j0rsa/transmission-rpc"
license = "MIT" license = "MIT"
readme = "README.md" readme = "README.md"
description = "Transmission JRPC client" description = "Transmission JRPC client"
keywords = ["transmission", "torrent", "jrpc"] keywords = ["transmission", "torrent", "jrpc"]
categories = ["api-bindings"] categories = ["api-bindings"]
include = [ include = ["**/*.rs", "Cargo.toml"]
"**/*.rs",
"Cargo.toml",
]
[dependencies] [dependencies]
reqwest = { version = "0.11.2", features = ["json", "rustls-tls"], default-features = false } reqwest = { version = "0.11.2", features = [
"json",
"rustls-tls",
], default-features = false }
serde = { version = "1.0.124", features = ["derive"] } serde = { version = "1.0.124", features = ["derive"] }
enum-iterator = "0.8.1" enum-iterator = "0.8.1"

View File

@ -12,14 +12,14 @@ async fn main() -> Result<()> {
let url = env::var("TURL")?; let url = env::var("TURL")?;
let mut client; let mut client;
if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) { if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) {
client = TransClient::with_auth(&url, BasicAuth {user, password}); client = TransClient::with_auth(&url, BasicAuth { user, password });
} else { } else {
client = TransClient::new(&url); client = TransClient::new(&url);
} }
let response: Result<RpcResponse<BlocklistUpdate>> = client.blocklist_update().await; let response: Result<RpcResponse<BlocklistUpdate>> = client.blocklist_update().await;
match response { match response {
Ok(_) => println!("Yay!"), Ok(_) => println!("Yay!"),
Err(_) => panic!("Oh no!") Err(_) => panic!("Oh no!"),
} }
println!("Rpc response is ok: {}", response?.is_ok()); println!("Rpc response is ok: {}", response?.is_ok());
Ok(()) Ok(())

View File

@ -2,7 +2,7 @@ extern crate transmission_rpc;
use dotenv::dotenv; use dotenv::dotenv;
use std::env; use std::env;
use transmission_rpc::types::{BasicAuth, Result, RpcResponse, FreeSpace}; use transmission_rpc::types::{BasicAuth, FreeSpace, Result, RpcResponse};
use transmission_rpc::TransClient; use transmission_rpc::TransClient;
#[tokio::main] #[tokio::main]
@ -13,14 +13,14 @@ async fn main() -> Result<()> {
let dir = env::var("TDIR")?; let dir = env::var("TDIR")?;
let mut client; let mut client;
if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) { if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) {
client = TransClient::with_auth(&url, BasicAuth {user, password}); client = TransClient::with_auth(&url, BasicAuth { user, password });
} else { } else {
client = TransClient::new(&url); client = TransClient::new(&url);
} }
let response: Result<RpcResponse<FreeSpace>> = client.free_space(dir).await; let response: Result<RpcResponse<FreeSpace>> = client.free_space(dir).await;
match response { match response {
Ok(_) => println!("Yay!"), Ok(_) => println!("Yay!"),
Err(_) => panic!("Oh no!") Err(_) => panic!("Oh no!"),
} }
println!("Rpc response is ok: {}", response?.is_ok()); println!("Rpc response is ok: {}", response?.is_ok());
Ok(()) Ok(())

View File

@ -2,7 +2,7 @@ extern crate transmission_rpc;
use dotenv::dotenv; use dotenv::dotenv;
use std::env; use std::env;
use transmission_rpc::types::{BasicAuth, Result, RpcResponse, PortTest}; use transmission_rpc::types::{BasicAuth, PortTest, Result, RpcResponse};
use transmission_rpc::TransClient; use transmission_rpc::TransClient;
#[tokio::main] #[tokio::main]
@ -12,14 +12,14 @@ async fn main() -> Result<()> {
let url = env::var("TURL")?; let url = env::var("TURL")?;
let mut client; let mut client;
if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) { if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) {
client = TransClient::with_auth(&url, BasicAuth {user, password}); client = TransClient::with_auth(&url, BasicAuth { user, password });
} else { } else {
client = TransClient::new(&url); client = TransClient::new(&url);
} }
let response: Result<RpcResponse<PortTest>> = client.port_test().await; let response: Result<RpcResponse<PortTest>> = client.port_test().await;
match response { match response {
Ok(_) => println!("Yay!"), Ok(_) => println!("Yay!"),
Err(_) => panic!("Oh no!") Err(_) => panic!("Oh no!"),
} }
println!("Rpc response is ok: {}", response?.is_ok()); println!("Rpc response is ok: {}", response?.is_ok());
Ok(()) Ok(())

View File

@ -12,14 +12,14 @@ async fn main() -> Result<()> {
let url = env::var("TURL")?; let url = env::var("TURL")?;
let mut client; let mut client;
if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) { if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) {
client = TransClient::with_auth(&url, BasicAuth {user, password}); client = TransClient::with_auth(&url, BasicAuth { user, password });
} else { } else {
client = TransClient::new(&url); client = TransClient::new(&url);
} }
let response: Result<RpcResponse<SessionClose>> = client.session_close().await; let response: Result<RpcResponse<SessionClose>> = client.session_close().await;
match response { match response {
Ok(_) => println!("Yay!"), Ok(_) => println!("Yay!"),
Err(_) => panic!("Oh no!") Err(_) => panic!("Oh no!"),
} }
println!("Rpc response is ok: {}", response?.is_ok()); println!("Rpc response is ok: {}", response?.is_ok());
Ok(()) Ok(())

View File

@ -12,14 +12,14 @@ async fn main() -> Result<()> {
let url = env::var("TURL")?; let url = env::var("TURL")?;
let mut client; let mut client;
if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) { if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) {
client = TransClient::with_auth(&url, BasicAuth {user, password}); client = TransClient::with_auth(&url, BasicAuth { user, password });
} else { } else {
client = TransClient::new(&url); client = TransClient::new(&url);
} }
let response: Result<RpcResponse<SessionGet>> = client.session_get().await; let response: Result<RpcResponse<SessionGet>> = client.session_get().await;
match response { match response {
Ok(_) => println!("Yay!"), Ok(_) => println!("Yay!"),
Err(_) => panic!("Oh no!") Err(_) => panic!("Oh no!"),
} }
println!("Rpc response is ok: {}", response?.is_ok()); println!("Rpc response is ok: {}", response?.is_ok());
Ok(()) Ok(())

View File

@ -12,14 +12,14 @@ async fn main() -> Result<()> {
let url = env::var("TURL")?; let url = env::var("TURL")?;
let mut client; let mut client;
if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) { if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) {
client = TransClient::with_auth(&url, BasicAuth {user, password}); client = TransClient::with_auth(&url, BasicAuth { user, password });
} else { } else {
client = TransClient::new(&url); client = TransClient::new(&url);
} }
let response: Result<RpcResponse<SessionStats>> = client.session_stats().await; let response: Result<RpcResponse<SessionStats>> = client.session_stats().await;
match response { match response {
Ok(_) => println!("Yay!"), Ok(_) => println!("Yay!"),
Err(_) => panic!("Oh no!") Err(_) => panic!("Oh no!"),
} }
println!("Rpc response is ok: {}", response?.is_ok()); println!("Rpc response is ok: {}", response?.is_ok());
Ok(()) Ok(())

View File

@ -2,8 +2,7 @@ extern crate transmission_rpc;
use dotenv::dotenv; use dotenv::dotenv;
use std::env; use std::env;
use transmission_rpc::types::{BasicAuth, Result, RpcResponse}; use transmission_rpc::types::{BasicAuth, Id, Nothing, Result, RpcResponse, TorrentAction};
use transmission_rpc::types::{Id, Nothing, TorrentAction};
use transmission_rpc::TransClient; use transmission_rpc::TransClient;
#[tokio::main] #[tokio::main]
@ -13,13 +12,17 @@ async fn main() -> Result<()> {
let url = env::var("TURL")?; let url = env::var("TURL")?;
let mut client; let mut client;
if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) { if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) {
client = TransClient::with_auth(&url, BasicAuth {user, password}); client = TransClient::with_auth(&url, BasicAuth { user, password });
} else { } else {
client = TransClient::new(&url); client = TransClient::new(&url);
} }
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()); 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()); println!("Stop result: {:?}", &res2.is_ok());
Ok(()) Ok(())

View File

@ -2,8 +2,7 @@ extern crate transmission_rpc;
use dotenv::dotenv; use dotenv::dotenv;
use std::env; use std::env;
use transmission_rpc::types::{BasicAuth, Result, RpcResponse}; use transmission_rpc::types::{BasicAuth, Result, RpcResponse, TorrentAddArgs, TorrentAddedOrDuplicate};
use transmission_rpc::types::{TorrentAddArgs, TorrentAddedOrDuplicate};
use transmission_rpc::TransClient; use transmission_rpc::TransClient;
#[tokio::main] #[tokio::main]
@ -13,12 +12,15 @@ async fn main() -> Result<()> {
let url = env::var("TURL")?; let url = env::var("TURL")?;
let mut client; let mut client;
if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) { if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) {
client = TransClient::with_auth(&url, BasicAuth {user, password}); client = TransClient::with_auth(&url, BasicAuth { user, password });
} else { } else {
client = TransClient::new(&url); client = TransClient::new(&url);
} }
let add: TorrentAddArgs = TorrentAddArgs { let add: TorrentAddArgs = TorrentAddArgs {
filename: Some("https://releases.ubuntu.com/20.04/ubuntu-20.04.2.0-desktop-amd64.iso.torrent".to_string()), filename: Some(
"https://releases.ubuntu.com/20.04/ubuntu-20.04.2.0-desktop-amd64.iso.torrent"
.to_string(),
),
..TorrentAddArgs::default() ..TorrentAddArgs::default()
}; };
let res: RpcResponse<TorrentAddedOrDuplicate> = client.torrent_add(add).await?; let res: RpcResponse<TorrentAddedOrDuplicate> = client.torrent_add(add).await?;

View File

@ -2,8 +2,9 @@ extern crate transmission_rpc;
use dotenv::dotenv; use dotenv::dotenv;
use std::env; use std::env;
use transmission_rpc::types::{BasicAuth, Result, RpcResponse}; use transmission_rpc::types::{
use transmission_rpc::types::{Id, Torrent, TorrentGetField, Torrents}; BasicAuth, Id, Result, RpcResponse, Torrent, TorrentGetField, Torrents,
};
use transmission_rpc::TransClient; use transmission_rpc::TransClient;
#[tokio::main] #[tokio::main]
@ -13,7 +14,7 @@ async fn main() -> Result<()> {
let url = env::var("TURL")?; let url = env::var("TURL")?;
let mut client; let mut client;
if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) { if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) {
client = TransClient::with_auth(&url, BasicAuth {user, password}); client = TransClient::with_auth(&url, BasicAuth { user, password });
} else { } else {
client = TransClient::new(&url); client = TransClient::new(&url);
} }
@ -27,15 +28,23 @@ async fn main() -> Result<()> {
.collect(); .collect();
println!("{:#?}", names); println!("{:#?}", names);
let res1: RpcResponse<Torrents<Torrent>> = client.torrent_get( let res1: RpcResponse<Torrents<Torrent>> = client
.torrent_get(
Some(vec![TorrentGetField::Id, TorrentGetField::Name]), Some(vec![TorrentGetField::Id, TorrentGetField::Name]),
Some(vec![Id::Id(1), Id::Id(2), Id::Id(3)]), Some(vec![Id::Id(1), Id::Id(2), Id::Id(3)]),
).await?; )
.await?;
let first_three: Vec<String> = res1 let first_three: Vec<String> = res1
.arguments .arguments
.torrents .torrents
.iter() .iter()
.map(|it| {format!("{}. {}", &it.id.as_ref().unwrap(), &it.name.as_ref().unwrap())}) .map(|it| {
format!(
"{}. {}",
&it.id.as_ref().unwrap(),
&it.name.as_ref().unwrap()
)
})
.collect(); .collect();
println!("{:#?}", first_three); println!("{:#?}", first_three);
@ -46,13 +55,23 @@ async fn main() -> Result<()> {
TorrentGetField::HashString, TorrentGetField::HashString,
TorrentGetField::Name, TorrentGetField::Name,
]), ]),
Some(vec![Id::Hash(String::from("64b0d9a53ac9cd1002dad1e15522feddb00152fe",))]), Some(vec![Id::Hash(String::from(
).await?; "64b0d9a53ac9cd1002dad1e15522feddb00152fe",
))]),
)
.await?;
let info: Vec<String> = res2 let info: Vec<String> = res2
.arguments .arguments
.torrents .torrents
.iter() .iter()
.map(|it| {format!("{:5}. {:^45} {}", &it.id.as_ref().unwrap(), &it.hash_string.as_ref().unwrap(), &it.name.as_ref().unwrap())}) .map(|it| {
format!(
"{:5}. {:^45} {}",
&it.id.as_ref().unwrap(),
&it.hash_string.as_ref().unwrap(),
&it.name.as_ref().unwrap()
)
})
.collect(); .collect();
println!("{:#?}", info); println!("{:#?}", info);

View File

@ -2,8 +2,7 @@ extern crate transmission_rpc;
use dotenv::dotenv; use dotenv::dotenv;
use std::env; use std::env;
use transmission_rpc::types::{BasicAuth, Result, RpcResponse}; use transmission_rpc::types::{BasicAuth, Id, Nothing, Result, RpcResponse};
use transmission_rpc::types::{Id, Nothing};
use transmission_rpc::TransClient; use transmission_rpc::TransClient;
#[tokio::main] #[tokio::main]
@ -13,7 +12,7 @@ async fn main() -> Result<()> {
let url = env::var("TURL")?; let url = env::var("TURL")?;
let mut client; let mut client;
if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) { if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) {
client = TransClient::with_auth(&url, BasicAuth {user, password}); client = TransClient::with_auth(&url, BasicAuth { user, password });
} else { } else {
client = TransClient::new(&url); client = TransClient::new(&url);
} }

View File

@ -1,19 +1,27 @@
extern crate transmission_rpc; extern crate transmission_rpc;
use std::env;
use dotenv::dotenv; use dotenv::dotenv;
use std::env;
use transmission_rpc::types::{BasicAuth, Id, Result, RpcResponse, TorrentRenamePath};
use transmission_rpc::TransClient; use transmission_rpc::TransClient;
use transmission_rpc::types::{Result, RpcResponse, BasicAuth};
use transmission_rpc::types::{TorrentRenamePath, Id};
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
dotenv().ok(); dotenv().ok();
env_logger::init(); env_logger::init();
let url= env::var("TURL")?; 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, basic_auth); let mut client = TransClient::with_auth(&url, 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); println!("rename-path result: {:#?}", res);
Ok(()) Ok(())

View File

@ -2,8 +2,7 @@ extern crate transmission_rpc;
use dotenv::dotenv; use dotenv::dotenv;
use std::env; use std::env;
use transmission_rpc::types::{BasicAuth, Result, RpcResponse}; use transmission_rpc::types::{BasicAuth, Id, Nothing, Result, RpcResponse};
use transmission_rpc::types::{Id, Nothing};
use transmission_rpc::TransClient; use transmission_rpc::TransClient;
#[tokio::main] #[tokio::main]
@ -13,15 +12,17 @@ async fn main() -> Result<()> {
let url = env::var("TURL")?; let url = env::var("TURL")?;
let mut client; let mut client;
if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) { if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) {
client = TransClient::with_auth(&url, BasicAuth {user, password}); client = TransClient::with_auth(&url, BasicAuth { user, password });
} else { } else {
client = TransClient::new(&url); client = TransClient::new(&url);
} }
let res: RpcResponse<Nothing> = client.torrent_set_location( let res: RpcResponse<Nothing> = client
.torrent_set_location(
vec![Id::Id(1)], vec![Id::Id(1)],
String::from("/new/location"), String::from("/new/location"),
Option::from(false), Option::from(false),
).await?; )
.await?;
println!("Set-location result: {:?}", &res.is_ok()); println!("Set-location result: {:?}", &res.is_ok());
Ok(()) Ok(())

View File

@ -9,17 +9,11 @@ use serde::de::DeserializeOwned;
pub mod types; pub mod types;
use types::BasicAuth; use types::{
use types::BlocklistUpdate; BasicAuth, BlocklistUpdate, FreeSpace, Id, Nothing, PortTest, Result, RpcRequest, RpcResponse,
use types::SessionGet; RpcResponseArgument, SessionClose, SessionGet, SessionStats, Torrent, TorrentAction,
use types::SessionStats; TorrentAddArgs, TorrentAddedOrDuplicate, TorrentGetField, TorrentRenamePath, Torrents,
use types::SessionClose; };
use types::PortTest;
use types::FreeSpace;
use types::TorrentAction;
use types::{Id, Torrent, TorrentGetField, Torrents};
use types::{Nothing, Result, RpcRequest, RpcResponse, RpcResponseArgument, TorrentRenamePath};
use types::{TorrentAddArgs, TorrentAddedOrDuplicate};
const MAX_RETRIES: usize = 5; const MAX_RETRIES: usize = 5;
@ -49,6 +43,7 @@ pub struct TransClient {
impl TransClient { impl TransClient {
/// Returns HTTP(S) client with configured Basic Auth /// Returns HTTP(S) client with configured Basic Auth
#[must_use]
pub fn with_auth(url: &str, basic_auth: BasicAuth) -> TransClient { pub fn with_auth(url: &str, basic_auth: BasicAuth) -> TransClient {
TransClient { TransClient {
url: url.to_string(), url: url.to_string(),
@ -59,6 +54,7 @@ impl TransClient {
} }
/// Returns HTTP(S) client /// Returns HTTP(S) client
#[must_use]
pub fn new(url: &str) -> TransClient { pub fn new(url: &str) -> TransClient {
TransClient { TransClient {
url: url.to_string(), url: url.to_string(),
@ -71,11 +67,13 @@ impl TransClient {
/// Prepares a request for provided server and auth /// Prepares a request for provided server and auth
fn rpc_request(&self) -> reqwest::RequestBuilder { fn rpc_request(&self) -> reqwest::RequestBuilder {
if let Some(auth) = &self.auth { if let Some(auth) = &self.auth {
self.client.post(&self.url) self.client
.post(&self.url)
.basic_auth(&auth.user, Some(&auth.password)) .basic_auth(&auth.user, Some(&auth.password))
} else { } else {
self.client.post(&self.url) self.client.post(&self.url)
}.header(CONTENT_TYPE, "application/json") }
.header(CONTENT_TYPE, "application/json")
} }
/// Performs a session get call /// Performs a session get call
@ -547,10 +545,15 @@ impl TransClient {
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```
///
/// # Panics
/// Either metainfo or torrent filename must be set or this call will panic.
///
pub async fn torrent_add(&mut self, add: TorrentAddArgs) -> Result<RpcResponse<TorrentAdded>> { pub async fn torrent_add(&mut self, add: TorrentAddArgs) -> Result<RpcResponse<TorrentAdded>> {
if add.metainfo == None && add.filename == None { assert!(
panic!("Metainfo or Filename should be provided") !(add.metainfo == None && add.filename == None),
} "Metainfo or Filename should be provided"
);
self.call(RpcRequest::torrent_add(add)).await self.call(RpcRequest::torrent_add(add)).await
} }
@ -560,23 +563,21 @@ impl TransClient {
/// ///
/// Any IO Error or Deserialization error /// Any IO Error or Deserialization error
async fn call<RS>(&mut self, request: RpcRequest) -> Result<RpcResponse<RS>> async fn call<RS>(&mut self, request: RpcRequest) -> Result<RpcResponse<RS>>
where where
RS: RpcResponseArgument + DeserializeOwned + std::fmt::Debug, RS: RpcResponseArgument + DeserializeOwned + std::fmt::Debug,
{ {
let mut remaining_retries = MAX_RETRIES; let mut remaining_retries = MAX_RETRIES;
loop { loop {
if remaining_retries <= 0 { remaining_retries = remaining_retries
return Err(From::from(TransError::MaxRetriesReached)); .checked_sub(1)
} .ok_or(TransError::MaxRetriesReached)?;
remaining_retries -= 1;
info!("Loaded auth: {:?}", &self.auth); info!("Loaded auth: {:?}", &self.auth);
let rq = match &self.session_id { let rq = match &self.session_id {
None => self.rpc_request(), None => self.rpc_request(),
Some(id) => { Some(id) => self.rpc_request().header("X-Transmission-Session-Id", id),
self.rpc_request().header("X-Transmission-Session-Id", id) }
} .json(&request);
}.json(&request);
info!( info!(
"Request body: {:?}", "Request body: {:?}",
@ -586,23 +587,20 @@ impl TransClient {
); );
let rsp: reqwest::Response = rq.send().await?; let rsp: reqwest::Response = rq.send().await?;
match rsp.status() { if matches!(rsp.status(), StatusCode::CONFLICT) {
StatusCode::CONFLICT => { let session_id = rsp
let session_id = rsp.headers() .headers()
.get("X-Transmission-Session-Id") .get("X-Transmission-Session-Id")
.ok_or(TransError::NoSessionIdReceived)? .ok_or(TransError::NoSessionIdReceived)?
.to_str()?; .to_str()?;
self.session_id = Some(String::from(session_id)); self.session_id = Some(String::from(session_id));
info!("Got new session_id: {}. Retrying request.", session_id); info!("Got new session_id: {}. Retrying request.", session_id);
continue; } else {
} let rpc_response: RpcResponse<RS> = rsp.json().await?;
_ => { info!("Response body: {:#?}", rpc_response);
let rpc_response: RpcResponse<RS> = rsp.json().await?;
info!("Response body: {:#?}", rpc_response);
return Ok(rpc_response) return Ok(rpc_response);
}
} }
} }
} }
@ -633,7 +631,7 @@ mod tests {
let url = env::var("TURL")?; let url = env::var("TURL")?;
let mut client; let mut client;
if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) { if let (Ok(user), Ok(password)) = (env::var("TUSER"), env::var("TPWD")) {
client = TransClient::with_auth(&url, BasicAuth {user, password}); client = TransClient::with_auth(&url, BasicAuth { user, password });
} else { } else {
client = TransClient::new(&url); client = TransClient::new(&url);
} }

View File

@ -9,24 +9,13 @@ pub struct BasicAuth {
pub password: String, pub password: String,
} }
pub use self::request::ArgumentFields;
pub use self::request::Id;
pub(crate) use self::request::RpcRequest; pub(crate) use self::request::RpcRequest;
pub use self::request::TorrentAction; pub use self::request::{
pub use self::request::TorrentAddArgs; ArgumentFields, Id, TorrentAction, TorrentAddArgs, TorrentGetField, TorrentRenamePathArgs,
pub use self::request::TorrentGetField; };
pub use self::request::TorrentRenamePathArgs;
pub use self::response::Nothing;
pub use self::response::RpcResponse;
pub(crate) use self::response::RpcResponseArgument; pub(crate) use self::response::RpcResponseArgument;
pub use self::response::SessionGet; pub use self::response::{
pub use self::response::SessionStats; BlocklistUpdate, FreeSpace, Nothing, PortTest, RpcResponse, SessionClose, SessionGet,
pub use self::response::SessionClose; SessionStats, Torrent, TorrentAddedOrDuplicate, TorrentRenamePath, Torrents,
pub use self::response::BlocklistUpdate; };
pub use self::response::PortTest;
pub use self::response::FreeSpace;
pub use self::response::Torrent;
pub use self::response::TorrentAddedOrDuplicate;
pub use self::response::Torrents;
pub use self::response::TorrentRenamePath;

View File

@ -40,7 +40,7 @@ impl RpcRequest {
pub fn free_space(path: String) -> RpcRequest { pub fn free_space(path: String) -> RpcRequest {
RpcRequest { RpcRequest {
method: String::from("free-space"), method: String::from("free-space"),
arguments: Some(Args::FreeSpaceArgs(FreeSpaceArgs { path })), arguments: Some(Args::FreeSpace(FreeSpaceArgs { path })),
} }
} }
@ -53,13 +53,13 @@ impl RpcRequest {
pub fn torrent_get(fields: Option<Vec<TorrentGetField>>, ids: Option<Vec<Id>>) -> RpcRequest { pub fn torrent_get(fields: Option<Vec<TorrentGetField>>, ids: Option<Vec<Id>>) -> RpcRequest {
let string_fields = fields let string_fields = fields
.unwrap_or(TorrentGetField::all()) .unwrap_or_else(TorrentGetField::all)
.iter() .iter()
.map(|f| f.to_str()) .map(TorrentGetField::to_str)
.collect(); .collect();
RpcRequest { RpcRequest {
method: String::from("torrent-get"), method: String::from("torrent-get"),
arguments: Some(Args::TorrentGetArgs(TorrentGetArgs { arguments: Some(Args::TorrentGet(TorrentGetArgs {
fields: Some(string_fields), fields: Some(string_fields),
ids, ids,
})), })),
@ -69,7 +69,7 @@ impl RpcRequest {
pub fn torrent_remove(ids: Vec<Id>, delete_local_data: bool) -> RpcRequest { pub fn torrent_remove(ids: Vec<Id>, delete_local_data: bool) -> RpcRequest {
RpcRequest { RpcRequest {
method: String::from("torrent-remove"), method: String::from("torrent-remove"),
arguments: Some(Args::TorrentRemoveArgs(TorrentRemoveArgs { arguments: Some(Args::TorrentRemove(TorrentRemoveArgs {
ids, ids,
delete_local_data, delete_local_data,
})), })),
@ -79,21 +79,25 @@ impl RpcRequest {
pub fn torrent_add(add: TorrentAddArgs) -> RpcRequest { pub fn torrent_add(add: TorrentAddArgs) -> RpcRequest {
RpcRequest { RpcRequest {
method: String::from("torrent-add"), method: String::from("torrent-add"),
arguments: Some(Args::TorrentAddArgs(add)), arguments: Some(Args::TorrentAdd(add)),
} }
} }
pub fn torrent_action(action: TorrentAction, ids: Vec<Id>) -> RpcRequest { pub fn torrent_action(action: TorrentAction, ids: Vec<Id>) -> RpcRequest {
RpcRequest { RpcRequest {
method: action.to_str(), method: action.to_str(),
arguments: Some(Args::TorrentActionArgs(TorrentActionArgs { ids })), arguments: Some(Args::TorrentAction(TorrentActionArgs { ids })),
} }
} }
pub fn torrent_set_location(ids: Vec<Id>, location: String, move_from: Option<bool>, ) -> RpcRequest { pub fn torrent_set_location(
ids: Vec<Id>,
location: String,
move_from: Option<bool>,
) -> RpcRequest {
RpcRequest { RpcRequest {
method: String::from("torrent-set-location"), method: String::from("torrent-set-location"),
arguments: Some(Args::TorrentSetLocationArgs(TorrentSetLocationArgs { arguments: Some(Args::TorrentSetLocation(TorrentSetLocationArgs {
ids, ids,
location, location,
move_from, move_from,
@ -104,11 +108,11 @@ impl RpcRequest {
pub fn torrent_rename_path(ids: Vec<Id>, path: String, name: String) -> RpcRequest { pub fn torrent_rename_path(ids: Vec<Id>, path: String, name: String) -> RpcRequest {
RpcRequest { RpcRequest {
method: String::from("torrent-rename-path"), method: String::from("torrent-rename-path"),
arguments: Some(Args::TorrentRenamePathArgs(TorrentRenamePathArgs { arguments: Some(Args::TorrentRenamePath(TorrentRenamePathArgs {
ids, ids,
path, path,
name name,
})) })),
} }
} }
} }
@ -118,13 +122,13 @@ impl ArgumentFields for TorrentGetField {}
#[derive(Serialize, Debug, Clone)] #[derive(Serialize, Debug, Clone)]
#[serde(untagged)] #[serde(untagged)]
pub enum Args { pub enum Args {
FreeSpaceArgs(FreeSpaceArgs), FreeSpace(FreeSpaceArgs),
TorrentGetArgs(TorrentGetArgs), TorrentGet(TorrentGetArgs),
TorrentActionArgs(TorrentActionArgs), TorrentAction(TorrentActionArgs),
TorrentRemoveArgs(TorrentRemoveArgs), TorrentRemove(TorrentRemoveArgs),
TorrentAddArgs(TorrentAddArgs), TorrentAdd(TorrentAddArgs),
TorrentSetLocationArgs(TorrentSetLocationArgs), TorrentSetLocation(TorrentSetLocationArgs),
TorrentRenamePathArgs(TorrentRenamePathArgs), TorrentRenamePath(TorrentRenamePathArgs),
} }
#[derive(Serialize, Debug, Clone)] #[derive(Serialize, Debug, Clone)]
@ -185,7 +189,7 @@ pub enum Id {
Hash(String), Hash(String),
} }
#[derive(Serialize, Debug, Clone)] #[derive(Serialize, Debug, Default, Clone)]
pub struct TorrentAddArgs { pub struct TorrentAddArgs {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub cookies: Option<String>, pub cookies: Option<String>,
@ -224,26 +228,7 @@ pub struct TorrentAddArgs {
pub priority_normal: Option<Vec<i32>>, pub priority_normal: Option<Vec<i32>>,
} }
impl Default for TorrentAddArgs { #[derive(Clone, Copy, IntoEnumIterator)]
fn default() -> Self {
TorrentAddArgs {
cookies: None,
download_dir: None,
filename: None,
metainfo: None,
paused: None,
peer_limit: None,
bandwidth_priority: None,
files_wanted: None,
files_unwanted: None,
priority_high: None,
priority_low: None,
priority_normal: None,
}
}
}
#[derive(Clone, IntoEnumIterator)]
pub enum TorrentGetField { pub enum TorrentGetField {
Id, Id,
Addeddate, Addeddate,
@ -278,16 +263,18 @@ pub enum TorrentGetField {
Webseedssendingtous, Webseedssendingtous,
Wanted, Wanted,
Priorities, Priorities,
Filestats Filestats,
} }
impl TorrentGetField { impl TorrentGetField {
#[must_use]
pub fn all() -> Vec<TorrentGetField> { pub fn all() -> Vec<TorrentGetField> {
TorrentGetField::into_enum_iter().collect() TorrentGetField::into_enum_iter().collect()
} }
} }
impl TorrentGetField { impl TorrentGetField {
#[must_use]
pub fn to_str(&self) -> String { pub fn to_str(&self) -> String {
match self { match self {
TorrentGetField::Id => "id", TorrentGetField::Id => "id",
@ -324,10 +311,12 @@ impl TorrentGetField {
TorrentGetField::Wanted => "wanted", TorrentGetField::Wanted => "wanted",
TorrentGetField::Priorities => "priorities", TorrentGetField::Priorities => "priorities",
TorrentGetField::Filestats => "fileStats", TorrentGetField::Filestats => "fileStats",
}.to_string() }
.to_string()
} }
} }
#[derive(Clone, Copy, Debug)]
pub enum TorrentAction { pub enum TorrentAction {
Start, Start,
Stop, Stop,
@ -337,6 +326,7 @@ pub enum TorrentAction {
} }
impl TorrentAction { impl TorrentAction {
#[must_use]
pub fn to_str(&self) -> String { pub fn to_str(&self) -> String {
match self { match self {
TorrentAction::Start => "torrent-start", TorrentAction::Start => "torrent-start",
@ -344,6 +334,7 @@ impl TorrentAction {
TorrentAction::StartNow => "torrent-start-now", TorrentAction::StartNow => "torrent-start-now",
TorrentAction::Verify => "torrent-verify", TorrentAction::Verify => "torrent-verify",
TorrentAction::Reannounce => "torrent-reannounce", TorrentAction::Reannounce => "torrent-reannounce",
}.to_string() }
.to_string()
} }
} }

View File

@ -142,7 +142,7 @@ pub struct Stats {
#[serde(rename = "secondsActive")] #[serde(rename = "secondsActive")]
pub seconds_active: i64, pub seconds_active: i64,
#[serde(rename = "sessionCount")] #[serde(rename = "sessionCount")]
pub session_count: Option<i32> pub session_count: Option<i32>,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
@ -187,10 +187,9 @@ pub enum TorrentAddedOrDuplicate {
impl RpcResponseArgument for TorrentAddedOrDuplicate {} impl RpcResponseArgument for TorrentAddedOrDuplicate {}
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct TorrentRenamePath{ pub struct TorrentRenamePath {
pub path: Option<String>, pub path: Option<String>,
pub name: Option<String>, pub name: Option<String>,
pub id: Option<i64>, pub id: Option<i64>,
} }
impl RpcResponseArgument for TorrentRenamePath {} impl RpcResponseArgument for TorrentRenamePath {}