Remove main.rs, add common.rs, bug fixes, added functions, etc
This commit is contained in:
parent
e3e9e26be4
commit
c339592689
|
@ -2,6 +2,27 @@
|
|||
# It is not intended for manual editing.
|
||||
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]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
|
@ -581,6 +602,7 @@ dependencies = [
|
|||
"serde_repr",
|
||||
"serde_with",
|
||||
"tokio",
|
||||
"tokio-test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -872,6 +894,30 @@ dependencies = [
|
|||
"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]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.3"
|
||||
|
|
|
@ -12,4 +12,7 @@ serde_with = "1.14.0"
|
|||
serde_repr = "0.1"
|
||||
|
||||
reqwest = { version = "0.11", features = ["cookies", "multipart"] }
|
||||
tokio = { version = "1.19.2" }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.19.2" }
|
||||
tokio-test = "0.4.2"
|
|
@ -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 url: String,
|
||||
|
@ -25,6 +27,16 @@ impl QBittorrentClient {
|
|||
|
||||
/// 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<()> {
|
||||
// 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
|
||||
let resp = self.client.post(format!("{}/api/v2/auth/login", url))
|
||||
.form(&[
|
||||
|
@ -59,10 +71,23 @@ impl QBittorrentClient {
|
|||
}
|
||||
|
||||
/// 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()) {
|
||||
let mut url = format!("{}/api/v2/torrents/info", conn.url.clone());
|
||||
|
||||
if let Some(params) = params {
|
||||
let mut params: &str = ¶ms.to_params();
|
||||
|
||||
// Remove leading &
|
||||
if params.starts_with("&") {
|
||||
params = ¶ms[1..params.len() - 1];
|
||||
}
|
||||
|
||||
url.push_str(&format!("?{}", params));
|
||||
}
|
||||
|
||||
// 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())
|
||||
.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.
|
||||
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()) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
24
src/lib.rs
24
src/lib.rs
|
@ -1,8 +1,22 @@
|
|||
pub mod torrent;
|
||||
pub use torrent::*;
|
||||
|
||||
pub mod client;
|
||||
pub use client::*;
|
||||
|
||||
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!");
|
||||
}
|
||||
}
|
23
src/main.rs
23
src/main.rs
|
@ -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!");
|
||||
}
|
|
@ -145,7 +145,7 @@ pub struct TorrentInfo {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
/// Some error occurred, applies to paused torrents
|
||||
#[serde(rename = "error")]
|
||||
|
|
Loading…
Reference in New Issue