Remove main.rs, add common.rs, bug fixes, added functions, etc

This commit is contained in:
seanomik 2022-06-22 22:04:46 -04:00
parent e3e9e26be4
commit c339592689
7 changed files with 291 additions and 33 deletions

46
Cargo.lock generated
View File

@ -2,6 +2,27 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "async-stream"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e"
dependencies = [
"async-stream-impl",
"futures-core",
]
[[package]]
name = "async-stream-impl"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -581,6 +602,7 @@ dependencies = [
"serde_repr", "serde_repr",
"serde_with", "serde_with",
"tokio", "tokio",
"tokio-test",
] ]
[[package]] [[package]]
@ -872,6 +894,30 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-stream"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-test"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3"
dependencies = [
"async-stream",
"bytes",
"futures-core",
"tokio",
"tokio-stream",
]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.3" version = "0.7.3"

View File

@ -12,4 +12,7 @@ serde_with = "1.14.0"
serde_repr = "0.1" serde_repr = "0.1"
reqwest = { version = "0.11", features = ["cookies", "multipart"] } reqwest = { version = "0.11", features = ["cookies", "multipart"] }
tokio = { version = "1.19.2" }
[dev-dependencies]
tokio = { version = "1.19.2" }
tokio-test = "0.4.2"

View File

@ -1,4 +1,6 @@
use crate::{error::ClientError, TorrentInfo, TorrentTracker, TorrentUpload}; use serde_json::error::Category;
use crate::{error::ClientError, torrent::{TorrentInfo, TorrentTracker, TorrentUpload}, common::*};
pub struct ConnectionInfo { pub struct ConnectionInfo {
pub url: String, pub url: String,
@ -25,6 +27,16 @@ impl QBittorrentClient {
/// Login to qBittorrent. This must be ran so that the client can make requests. /// Login to qBittorrent. This must be ran so that the client can make requests.
pub async fn login(&mut self, url: &str, username: &str, password: &str) -> ClientResult<()> { pub async fn login(&mut self, url: &str, username: &str, password: &str) -> ClientResult<()> {
// Remove trailing slash if necessary
let url = if url.ends_with("/") {
let mut chars = url.chars();
chars.next_back();
chars.as_str()
} else {
url
};
// Send response to get auth string // Send response to get auth string
let resp = self.client.post(format!("{}/api/v2/auth/login", url)) let resp = self.client.post(format!("{}/api/v2/auth/login", url))
.form(&[ .form(&[
@ -59,10 +71,23 @@ impl QBittorrentClient {
} }
/// Get a list of all torrents in the client. /// Get a list of all torrents in the client.
pub async fn get_torrent_list(&self) -> ClientResult<Vec<TorrentInfo>> { pub async fn get_torrent_list(&self, params: Option<GetTorrentListParams>) -> ClientResult<Vec<TorrentInfo>> {
if let (Some(auth_string), Some(conn)) = (self.auth_string.as_ref(), self.connection_info.as_ref()) { if let (Some(auth_string), Some(conn)) = (self.auth_string.as_ref(), self.connection_info.as_ref()) {
let mut url = format!("{}/api/v2/torrents/info", conn.url.clone());
if let Some(params) = params {
let mut params: &str = &params.to_params();
// Remove leading &
if params.starts_with("&") {
params = &params[1..params.len() - 1];
}
url.push_str(&format!("?{}", params));
}
// Construct and send request to qbittorrent // Construct and send request to qbittorrent
let resp = self.client.post(format!("{}/api/v2/torrents/info", conn.url.clone())) let resp = self.client.post(url)
.header(reqwest::header::COOKIE, auth_string.clone()) .header(reqwest::header::COOKIE, auth_string.clone())
.send().await?.error_for_status()?; .send().await?.error_for_status()?;
@ -115,6 +140,24 @@ impl QBittorrentClient {
} }
} }
/// Add multiple trackers to a torrent.
pub async fn add_torrent_trackers(&self, torrent: &TorrentInfo, trackers: Vec<String>) -> ClientResult<()> {
if let (Some(auth_string), Some(conn)) = (self.auth_string.as_ref(), self.connection_info.as_ref()) {
// Construct and send request to qbittorrent
let _resp = self.client.post(format!("{}/api/v2/torrents/addTrackers", conn.url.clone()))
.header(reqwest::header::COOKIE, auth_string.clone())
.form(&[
("hash", torrent.hash.clone()),
("urls", trackers.join("\n")),
])
.send().await?.error_for_status()?;
Ok(())
} else {
Err(ClientError::Authorization)
}
}
/// Replace a tracker url on a torrent. /// Replace a tracker url on a torrent.
pub async fn replace_torrent_tracker(&self, torrent: &TorrentInfo, old_url: String, new_url: String) -> ClientResult<()> { pub async fn replace_torrent_tracker(&self, torrent: &TorrentInfo, old_url: String, new_url: String) -> ClientResult<()> {
if let (Some(auth_string), Some(conn)) = (self.auth_string.as_ref(), self.connection_info.as_ref()) { if let (Some(auth_string), Some(conn)) = (self.auth_string.as_ref(), self.connection_info.as_ref()) {

175
src/common.rs Normal file
View File

@ -0,0 +1,175 @@
use serde_with::rust::seq_display_fromstr;
/// This module contains common structs, and functions that can be used
/// by other crates. This is re-exported in `abstracttorrent` and used in it.
#[derive(Debug, Clone)]
pub enum TorrentListFilter {
All,
Downloading,
Seeding,
Completed,
Paused,
Active,
Inactive,
Resumed,
Stalled,
StalledUploading,
StalledDownloading,
Errored,
}
impl TorrentListFilter {
pub fn to_string(&self) -> &str {
match *self {
TorrentListFilter::All => "all",
TorrentListFilter::Downloading => "downloading",
TorrentListFilter::Seeding => "seeding",
TorrentListFilter::Completed => "completed",
TorrentListFilter::Paused => "paused",
TorrentListFilter::Active => "active",
TorrentListFilter::Inactive => "inactive",
TorrentListFilter::Resumed => "resumed",
TorrentListFilter::Stalled => "stalled",
TorrentListFilter::StalledUploading => "stalled_uploading",
TorrentListFilter::StalledDownloading => "stalled_downloading",
TorrentListFilter::Errored => "errored",
}
}
}
#[derive(Default, Clone)]
pub struct GetTorrentListParams {
/// Filter torrent list by state
pub filter: Option<TorrentListFilter>,
/// Get torrents with the given category
pub category: Option<String>,
/// Get torrents with the given tag.
pub tag: Option<String>,
// TODO: Add `sort` support for TorrentInfo fields.
/// Enable reverse sorting.
pub reverse: Option<bool>,
/// Limit the number of results.
pub limit: Option<i32>,
/// Set offset.
pub offset: Option<i32>,
/// Filter by hashes.
pub hashes: Option<Vec<String>> // NOTE: Separated by `|`
}
impl GetTorrentListParams {
pub fn builder() -> GetTorrentListParamsBuilder {
GetTorrentListParamsBuilder::default()
}
pub fn to_params(&self) -> String {
let mut params = String::new();
if let Some(filter) = &self.filter {
params.push_str(&format!("&filter={}", filter.to_string()));
}
if let Some(category) = &self.category {
params.push_str(&format!("&category={}", category));
}
if let Some(tag) = &self.tag {
params.push_str(&format!("&tag={}", tag));
}
if let Some(reverse) = &self.reverse {
params.push_str(&format!("&reverse={}", reverse.to_string()));
}
if let Some(limit) = &self.limit {
params.push_str(&format!("&limit={}", limit));
}
if let Some(offset) = &self.limit {
params.push_str(&format!("&offset={}", offset));
}
if let Some(hashes) = &self.hashes {
let hashes = hashes.join("|");
params.push_str(&format!("&hashes={}", hashes));
}
params
}
}
#[derive(Default)]
pub struct GetTorrentListParamsBuilder {
param: GetTorrentListParams,
}
impl GetTorrentListParamsBuilder {
/// Set a filter
pub fn filter(&mut self, filter: TorrentListFilter) -> &mut Self {
self.param.filter = Some(filter);
self
}
/// Set a filter.
pub fn category(&mut self, category: &str) -> &mut Self {
self.param.category = Some(category.to_string());
self
}
/// Set a tag.
pub fn tag(&mut self, tag: &str) -> &mut Self {
self.param.tag = Some(tag.to_string());
self
}
/// Reverse the order of the results.
pub fn reverse(&mut self) -> &mut Self {
self.param.reverse = Some(true);
self
}
/// Set a limit on the number of results returned.
pub fn limit(&mut self, limit: i32) -> &mut Self {
self.param.limit = Some(limit);
self
}
/// Set an offset of the results.
pub fn offset(&mut self, offset: i32) -> &mut Self {
self.param.offset = Some(offset);
self
}
/// Add a hash to filter by.
pub fn hash(&mut self, hash: &str) -> &mut Self {
self.param.hashes.as_mut()
.unwrap_or(&mut vec![])
.push(hash.to_string());
self
}
/// Set the hashes to filter by.
pub fn hashes(&mut self, hashes: Vec<String>) -> &mut Self {
self.param.hashes = Some(hashes);
self
}
pub fn build(&self) -> GetTorrentListParams {
self.param.clone()
}
}

View File

@ -1,8 +1,22 @@
pub mod torrent; pub mod torrent;
pub use torrent::*;
pub mod client; pub mod client;
pub use client::*;
pub mod error; pub mod error;
pub use error::*; pub mod common;
#[cfg(test)]
mod tests {
macro_rules! block_on {
($e:expr) => {
tokio_test::block_on($e)
};
}
#[test]
fn test_login() {
let mut client = super::client::QBittorrentClient::new();
block_on!(client.login("http://localhost:8080", "admin", "adminadmin")).unwrap();
println!("Logged in!");
}
}

View File

@ -1,23 +0,0 @@
/// NOTE: USED FOR TESTING
pub mod torrent;
pub use torrent::*;
pub mod client;
pub use client::*;
pub mod error;
pub use error::*;
#[tokio::main]
async fn main() {
let mut client = QBittorrentClient::new();
client.login(
String::from("http://localhost:8080"),
String::from("admin"),
String::from("adminadmin")
).await.unwrap();
println!("Hello, world!");
}

View File

@ -145,7 +145,7 @@ pub struct TorrentInfo {
} }
/// An enum representing the state of a torrent in the client. /// An enum representing the state of a torrent in the client.
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
pub enum TorrentState { pub enum TorrentState {
/// Some error occurred, applies to paused torrents /// Some error occurred, applies to paused torrents
#[serde(rename = "error")] #[serde(rename = "error")]