Modularize qBittorrent API in separate file
This commit is contained in:
parent
c6d9a0c07d
commit
6d5958df3f
|
@ -1,7 +1,7 @@
|
||||||
use argh::FromArgs;
|
use argh::FromArgs;
|
||||||
|
|
||||||
use crate::action::ActionExec;
|
use crate::action::ActionExec;
|
||||||
use crate::api::qbittorrent::{ApiClient, RawApiClient};
|
use crate::api::qbittorrent::ApiClient;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
|
@ -12,9 +12,6 @@ pub struct GetAction {
|
||||||
#[argh(switch)]
|
#[argh(switch)]
|
||||||
/// the positional argument is a magnet link, not an infohash
|
/// the positional argument is a magnet link, not an infohash
|
||||||
magnet: bool,
|
magnet: bool,
|
||||||
#[argh(switch, short = 'r')]
|
|
||||||
/// return raw JSON response from API
|
|
||||||
raw: bool,
|
|
||||||
#[argh(switch, short = 'j')]
|
#[argh(switch, short = 'j')]
|
||||||
/// return parsed JSON response from API
|
/// return parsed JSON response from API
|
||||||
json: bool,
|
json: bool,
|
||||||
|
@ -25,18 +22,12 @@ pub struct GetAction {
|
||||||
|
|
||||||
impl ActionExec for GetAction {
|
impl ActionExec for GetAction {
|
||||||
fn exec(&self, config: &Config) -> Result<(), Error> {
|
fn exec(&self, config: &Config) -> Result<(), Error> {
|
||||||
if self.raw {
|
let api = ApiClient::from_config(&config)?;
|
||||||
let api = RawApiClient::from_config(&config)?;
|
if let Some(t) = api.get(&self.torrent)? {
|
||||||
let res = api.get(&self.torrent)?;
|
if self.json {
|
||||||
println!("{}", res);
|
println!("{}", &serde_json::to_string(&t).unwrap());
|
||||||
} else {
|
} else {
|
||||||
let api = ApiClient::from_config(&config)?;
|
println!("{}", t.hash);
|
||||||
if let Some(t) = api.get(&self.torrent)? {
|
|
||||||
if self.json {
|
|
||||||
println!("{}", &serde_json::to_string(&t).unwrap());
|
|
||||||
} else {
|
|
||||||
println!("{}", t.hash);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use argh::FromArgs;
|
use argh::FromArgs;
|
||||||
|
|
||||||
use crate::api::qbittorrent::{ApiClient, RawApiClient};
|
use crate::api::qbittorrent::ApiClient;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use crate::action::ActionExec;
|
use crate::action::ActionExec;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
@ -9,9 +9,6 @@ use crate::config::Config;
|
||||||
#[argh(subcommand, name = "list")]
|
#[argh(subcommand, name = "list")]
|
||||||
/// list existing torrents on qBittorrent
|
/// list existing torrents on qBittorrent
|
||||||
pub struct ListAction {
|
pub struct ListAction {
|
||||||
#[argh(switch, short = 'r')]
|
|
||||||
/// return raw JSON response from API
|
|
||||||
raw: bool,
|
|
||||||
#[argh(switch, short = 'j')]
|
#[argh(switch, short = 'j')]
|
||||||
/// return parsed JSON response from API
|
/// return parsed JSON response from API
|
||||||
json: bool,
|
json: bool,
|
||||||
|
@ -19,19 +16,15 @@ pub struct ListAction {
|
||||||
|
|
||||||
impl ActionExec for ListAction {
|
impl ActionExec for ListAction {
|
||||||
fn exec(&self, config: &Config) -> Result<(), Error> {
|
fn exec(&self, config: &Config) -> Result<(), Error> {
|
||||||
if self.raw {
|
let api = ApiClient::from_config(&config)?;
|
||||||
let api = RawApiClient::from_config(&config)?;
|
for torrent in api.list()? {
|
||||||
println!("{}", api.list()?);
|
if self.json {
|
||||||
} else {
|
println!("{}", &serde_json::to_string(&torrent).unwrap());
|
||||||
let api = ApiClient::from_config(&config)?;
|
} else {
|
||||||
for torrent in api.list()? {
|
println!("{}", torrent.hash);
|
||||||
if self.json {
|
|
||||||
println!("{}", &serde_json::to_string(&torrent).unwrap());
|
|
||||||
} else {
|
|
||||||
println!("{}", torrent.hash);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
94
src/api/qbittorrent/asynchronous.rs
Normal file
94
src/api/qbittorrent/asynchronous.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
use snafu::prelude::*;
|
||||||
|
|
||||||
|
use crate::Config;
|
||||||
|
use crate::api::{Torrent, TorrentList, IntoTorrent, TorrentTracker};
|
||||||
|
use crate::api::qbittorrent::{QBittorrentListTorrent, RawAsyncApiClient};
|
||||||
|
use crate::error::{Error, FailedDeserializeSnafu as DeserializeError};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AsyncApiClient {
|
||||||
|
pub raw_api: RawAsyncApiClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncApiClient {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn from_config(config: &Config) -> Result<AsyncApiClient, Error> {
|
||||||
|
Self::new(&config.format_host(), &config.qbittorrent.login, &config.qbittorrent.password).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn new(host: &str, login: &str, password: &str) -> Result<AsyncApiClient, Error> {
|
||||||
|
let api = RawAsyncApiClient::login(host, login, password).await?;
|
||||||
|
|
||||||
|
Ok(AsyncApiClient {
|
||||||
|
raw_api: api,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn add(&self, magnet: &str, paused: bool) -> Result<(), Error> {
|
||||||
|
let res = self.raw_api.add(magnet, paused).await?;
|
||||||
|
if res == "Ok." {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::message(res.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn get(&self, hash: &str) -> Result<Option<Torrent>, Error> {
|
||||||
|
let res = self.raw_api.get(hash).await?;
|
||||||
|
if res == "" {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
// TODO: NOT OPTIMIZED AT ALL. API DOES NOT RETURN NAME/HASH OF TORRENT SO WE HAVE TO QUERY LIST...
|
||||||
|
let list = self.list().await?;
|
||||||
|
let filtered_list = list.into_iter().filter(|t| t.hash == hash).collect::<Vec<Torrent>>();
|
||||||
|
let torrent = filtered_list.first()
|
||||||
|
.unwrap_or_else(|| panic!("Torrent was 'get' but could not be found in 'list': {}", &hash));
|
||||||
|
Ok(Some(torrent.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
/// This method only exists because qBittorrent API 'properties' endpoint does not return torrent name/hash
|
||||||
|
/// so if you need to do a lot of 'get', please call 'list' once and use this method instead.
|
||||||
|
pub async fn get_with_cached_list(&self, hash: &str, list: &TorrentList) -> Result<Option<Torrent>, Error> {
|
||||||
|
let res = self.raw_api.get(hash).await?;
|
||||||
|
if res == "" {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
let filtered_list = list.clone().into_iter().filter(|t| t.hash == hash).collect::<Vec<Torrent>>();
|
||||||
|
let torrent = filtered_list.first()
|
||||||
|
.unwrap_or_else(|| panic!("Torrent was 'get' but could not be found in 'list': {}", &hash));
|
||||||
|
Ok(Some(torrent.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn list(&self) -> Result<TorrentList, Error> {
|
||||||
|
let res = self.raw_api.list().await?;
|
||||||
|
let concrete: Vec<QBittorrentListTorrent> = serde_json::from_str(&res).context(DeserializeError)?;
|
||||||
|
Ok(concrete.iter().map(|t| t.into_torrent()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn add_tracker(&self, hash: &str, url: &str) -> Result<(), Error> {
|
||||||
|
let _ = self.raw_api.add_tracker(hash, url).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn get_trackers(&self, hash: &str) -> Result<Vec<TorrentTracker>, Error> {
|
||||||
|
let res = self.raw_api.get_trackers(hash).await?;
|
||||||
|
let concrete: Vec<TorrentTracker> = serde_json::from_str(&res).context(DeserializeError)?;
|
||||||
|
let concrete_filter_dht = concrete.into_iter().filter(|t| t.is_tracker()).collect();
|
||||||
|
Ok(concrete_filter_dht)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn remove_tracker(&self, hash: &str, url: &str) -> Result<(), Error> {
|
||||||
|
let _ = self.raw_api.remove_tracker(hash, url).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,270 +1,10 @@
|
||||||
use snafu::prelude::*;
|
|
||||||
use qbittorrent_web_api::Api;
|
|
||||||
use tokio::runtime::{Builder, Runtime};
|
|
||||||
|
|
||||||
use crate::Config;
|
|
||||||
use crate::error::{Error, ApiSnafu as IOError, InternalApiSnafu as ApiError, FailedDeserializeSnafu as DeserializeError};
|
|
||||||
|
|
||||||
mod types;
|
mod types;
|
||||||
pub use types::QBittorrentPropertiesTorrent;
|
pub use types::QBittorrentPropertiesTorrent;
|
||||||
pub use types::QBittorrentListTorrent;
|
pub use types::QBittorrentListTorrent;
|
||||||
use crate::api::{Torrent, TorrentList, IntoTorrent, TorrentTracker};
|
|
||||||
|
|
||||||
pub fn blocking_runtime() -> std::io::Result<Runtime> {
|
mod raw_asynchronous;
|
||||||
Builder::new_current_thread()
|
pub use raw_asynchronous::RawAsyncApiClient;
|
||||||
.enable_all()
|
mod asynchronous;
|
||||||
.build()
|
pub use asynchronous::AsyncApiClient;
|
||||||
}
|
mod synchronous;
|
||||||
|
pub use synchronous::ApiClient;
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct UnauthenticatedRawAsyncApiClient;
|
|
||||||
|
|
||||||
impl UnauthenticatedRawAsyncApiClient {
|
|
||||||
pub async fn login(host: &str, login: &str, password: &str) -> Result<RawAsyncApiClient, Error> {
|
|
||||||
Ok(RawAsyncApiClient {
|
|
||||||
api: Api::login(host, login, password).await.context(ApiError)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RawAsyncApiClient {
|
|
||||||
pub api: qbittorrent_web_api::api_impl::Authenticated,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawAsyncApiClient {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn add(&self, magnet: &str, paused: bool) -> Result<String, Error> {
|
|
||||||
if paused {
|
|
||||||
self.api.torrent_management().add(magnet).paused("true").send_raw().await.context(ApiError)
|
|
||||||
} else {
|
|
||||||
self.api.torrent_management().add(magnet).send_raw().await.context(ApiError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn get(&self, hash: &str) -> Result<String, Error> {
|
|
||||||
self.api.torrent_management().properties_raw(hash).await.context(ApiError)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn list(&self) -> Result<String, Error> {
|
|
||||||
self.api.torrent_management().info().send_raw().await.context(ApiError)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn add_tracker(&self, hash: &str, url: &str) -> Result<String, Error> {
|
|
||||||
self.api.torrent_management().add_trackers_raw(hash, url).await.context(ApiError)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn get_trackers(&self, hash: &str) -> Result<String, Error> {
|
|
||||||
self.api.torrent_management().trackers_raw(hash).await.context(ApiError)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn remove_tracker(&self, hash: &str, url: &str) -> Result<String, Error> {
|
|
||||||
self.api.torrent_management().remove_trackers_raw(hash, &vec!(url)).await.context(ApiError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RawApiClient {
|
|
||||||
pub rt: Runtime,
|
|
||||||
pub api: RawAsyncApiClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawApiClient {
|
|
||||||
pub fn from_config(config: &Config) -> Result<RawApiClient, Error> {
|
|
||||||
Self::new(&config.format_host(), &config.qbittorrent.login, &config.qbittorrent.password)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Login into a qBittorrent backend and return a proper ApiClient instance
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new(host: &str, login: &str, password: &str) -> Result<RawApiClient, Error> {
|
|
||||||
let rt = blocking_runtime().context(IOError)?;
|
|
||||||
let api = rt.block_on(UnauthenticatedRawAsyncApiClient::login(host, login, password))?;
|
|
||||||
Ok(RawApiClient {
|
|
||||||
rt,
|
|
||||||
api,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn add(&self, magnet: &str, paused: bool) -> Result<String, Error> {
|
|
||||||
self.rt.block_on(self.api.add(magnet, paused))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn get(&self, hash: &str) -> Result<String, Error> {
|
|
||||||
self.rt.block_on(self.api.get(hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn list(&self) -> Result<String, Error> {
|
|
||||||
self.rt.block_on(self.api.list())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn add_tracker(&self, hash: &str, url: &str) -> Result<String, Error> {
|
|
||||||
self.rt.block_on(self.api.add_tracker(hash, url))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn get_trackers(&self, hash: &str) -> Result<String, Error> {
|
|
||||||
self.rt.block_on(self.api.get_trackers(hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn remove_tracker(&self, hash: &str, url: &str) -> Result<String, Error> {
|
|
||||||
self.rt.block_on(self.api.remove_tracker(hash, url))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct AsyncApiClient {
|
|
||||||
pub raw_api: RawAsyncApiClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsyncApiClient {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn from_config(config: &Config) -> Result<AsyncApiClient, Error> {
|
|
||||||
Self::new(&config.format_host(), &config.qbittorrent.login, &config.qbittorrent.password).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn new(host: &str, login: &str, password: &str) -> Result<AsyncApiClient, Error> {
|
|
||||||
let api = UnauthenticatedRawAsyncApiClient::login(host, login, password).await?;
|
|
||||||
|
|
||||||
Ok(AsyncApiClient {
|
|
||||||
raw_api: api,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn add(&self, magnet: &str, paused: bool) -> Result<(), Error> {
|
|
||||||
let res = self.raw_api.add(magnet, paused).await?;
|
|
||||||
if res == "Ok." {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Error::message(res.clone()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn get(&self, hash: &str) -> Result<Option<Torrent>, Error> {
|
|
||||||
let res = self.raw_api.get(hash).await?;
|
|
||||||
if res == "" {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
// TODO: NOT OPTIMIZED AT ALL. API DOES NOT RETURN NAME/HASH OF TORRENT SO WE HAVE TO QUERY LIST...
|
|
||||||
let list = self.list().await?;
|
|
||||||
let filtered_list = list.into_iter().filter(|t| t.hash == hash).collect::<Vec<Torrent>>();
|
|
||||||
let torrent = filtered_list.first()
|
|
||||||
.unwrap_or_else(|| panic!("Torrent was 'get' but could not be found in 'list': {}", &hash));
|
|
||||||
Ok(Some(torrent.clone()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
/// This method only exists because qBittorrent API 'properties' endpoint does not return torrent name/hash
|
|
||||||
/// so if you need to do a lot of 'get', please call 'list' once and use this method instead.
|
|
||||||
pub async fn get_with_cached_list(&self, hash: &str, list: &TorrentList) -> Result<Option<Torrent>, Error> {
|
|
||||||
let res = self.raw_api.get(hash).await?;
|
|
||||||
if res == "" {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
let filtered_list = list.clone().into_iter().filter(|t| t.hash == hash).collect::<Vec<Torrent>>();
|
|
||||||
let torrent = filtered_list.first()
|
|
||||||
.unwrap_or_else(|| panic!("Torrent was 'get' but could not be found in 'list': {}", &hash));
|
|
||||||
Ok(Some(torrent.clone()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn list(&self) -> Result<TorrentList, Error> {
|
|
||||||
let res = self.raw_api.list().await?;
|
|
||||||
let concrete: Vec<QBittorrentListTorrent> = serde_json::from_str(&res).context(DeserializeError)?;
|
|
||||||
Ok(concrete.iter().map(|t| t.into_torrent()).collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn add_tracker(&self, hash: &str, url: &str) -> Result<(), Error> {
|
|
||||||
let _ = self.raw_api.add_tracker(hash, url).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn get_trackers(&self, hash: &str) -> Result<Vec<TorrentTracker>, Error> {
|
|
||||||
let res = self.raw_api.get_trackers(hash).await?;
|
|
||||||
let concrete: Vec<TorrentTracker> = serde_json::from_str(&res).context(DeserializeError)?;
|
|
||||||
let concrete_filter_dht = concrete.into_iter().filter(|t| t.is_tracker()).collect();
|
|
||||||
Ok(concrete_filter_dht)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn remove_tracker(&self, hash: &str, url: &str) -> Result<(), Error> {
|
|
||||||
let _ = self.raw_api.remove_tracker(hash, url).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ApiClient {
|
|
||||||
pub rt: Runtime,
|
|
||||||
pub api: AsyncApiClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApiClient {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn from_config(config: &Config) -> Result<ApiClient, Error> {
|
|
||||||
Self::new(&config.format_host(), &config.qbittorrent.login, &config.qbittorrent.password)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Login into a qBittorrent backend and return a proper ApiClient instance
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn new(host: &str, login: &str, password: &str) -> Result<ApiClient, Error> {
|
|
||||||
let rt = blocking_runtime().context(IOError)?;
|
|
||||||
let api = rt.block_on(AsyncApiClient::new(host, login, password))?;
|
|
||||||
Ok(ApiClient {
|
|
||||||
rt,
|
|
||||||
api,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn add(&self, magnet: &str, paused: bool) -> Result<(), Error> {
|
|
||||||
self.rt.block_on(self.api.add(magnet, paused))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn get(&self, hash: &str) -> Result<Option<Torrent>, Error> {
|
|
||||||
self.rt.block_on(self.api.get(hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
// TODO: Workaround
|
|
||||||
pub fn get_with_cached_list(&self, hash: &str, list: &TorrentList) -> Result<Option<Torrent>, Error> {
|
|
||||||
self.rt.block_on(self.api.get_with_cached_list(hash, list))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn list(&self) -> Result<TorrentList, Error> {
|
|
||||||
self.rt.block_on(self.api.list())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn add_tracker(&self, hash: &str, url: &str) -> Result<(), Error> {
|
|
||||||
self.rt.block_on(self.api.add_tracker(hash, url))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn get_trackers(&self, hash: &str) -> Result<Vec<TorrentTracker>, Error> {
|
|
||||||
self.rt.block_on(self.api.get_trackers(hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn remove_tracker(&self, hash: &str, url: &str) -> Result<(), Error> {
|
|
||||||
self.rt.block_on(self.api.remove_tracker(hash, url))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
51
src/api/qbittorrent/raw_asynchronous.rs
Normal file
51
src/api/qbittorrent/raw_asynchronous.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use snafu::prelude::*;
|
||||||
|
use qbittorrent_web_api::Api;
|
||||||
|
|
||||||
|
use crate::error::{Error, InternalApiSnafu as ApiError};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RawAsyncApiClient {
|
||||||
|
pub api: qbittorrent_web_api::api_impl::Authenticated,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawAsyncApiClient {
|
||||||
|
pub async fn login(host: &str, login: &str, password: &str) -> Result<RawAsyncApiClient, Error> {
|
||||||
|
Ok(RawAsyncApiClient {
|
||||||
|
api: Api::login(host, login, password).await.context(ApiError)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn add(&self, magnet: &str, paused: bool) -> Result<String, Error> {
|
||||||
|
if paused {
|
||||||
|
self.api.torrent_management().add(magnet).paused("true").send_raw().await.context(ApiError)
|
||||||
|
} else {
|
||||||
|
self.api.torrent_management().add(magnet).send_raw().await.context(ApiError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn get(&self, hash: &str) -> Result<String, Error> {
|
||||||
|
self.api.torrent_management().properties_raw(hash).await.context(ApiError)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn list(&self) -> Result<String, Error> {
|
||||||
|
self.api.torrent_management().info().send_raw().await.context(ApiError)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn add_tracker(&self, hash: &str, url: &str) -> Result<String, Error> {
|
||||||
|
self.api.torrent_management().add_trackers_raw(hash, url).await.context(ApiError)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn get_trackers(&self, hash: &str) -> Result<String, Error> {
|
||||||
|
self.api.torrent_management().trackers_raw(hash).await.context(ApiError)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn remove_tracker(&self, hash: &str, url: &str) -> Result<String, Error> {
|
||||||
|
self.api.torrent_management().remove_trackers_raw(hash, &vec!(url)).await.context(ApiError)
|
||||||
|
}
|
||||||
|
}
|
68
src/api/qbittorrent/synchronous.rs
Normal file
68
src/api/qbittorrent/synchronous.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
use snafu::prelude::*;
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
|
use crate::{Config, Error};
|
||||||
|
use crate::api::{Torrent, TorrentList, TorrentTracker};
|
||||||
|
use crate::api::qbittorrent::AsyncApiClient;
|
||||||
|
use crate::error::ApiSnafu as IOError;
|
||||||
|
use crate::utils::blocking_runtime;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ApiClient {
|
||||||
|
pub rt: Runtime,
|
||||||
|
pub api: AsyncApiClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApiClient {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn from_config(config: &Config) -> Result<ApiClient, Error> {
|
||||||
|
Self::new(&config.format_host(), &config.qbittorrent.login, &config.qbittorrent.password)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Login into a qBittorrent backend and return a proper ApiClient instance
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn new(host: &str, login: &str, password: &str) -> Result<ApiClient, Error> {
|
||||||
|
let rt = blocking_runtime().context(IOError)?;
|
||||||
|
let api = rt.block_on(AsyncApiClient::new(host, login, password))?;
|
||||||
|
Ok(ApiClient {
|
||||||
|
rt,
|
||||||
|
api,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn add(&self, magnet: &str, paused: bool) -> Result<(), Error> {
|
||||||
|
self.rt.block_on(self.api.add(magnet, paused))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn get(&self, hash: &str) -> Result<Option<Torrent>, Error> {
|
||||||
|
self.rt.block_on(self.api.get(hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
// TODO: Workaround
|
||||||
|
pub fn get_with_cached_list(&self, hash: &str, list: &TorrentList) -> Result<Option<Torrent>, Error> {
|
||||||
|
self.rt.block_on(self.api.get_with_cached_list(hash, list))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn list(&self) -> Result<TorrentList, Error> {
|
||||||
|
self.rt.block_on(self.api.list())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn add_tracker(&self, hash: &str, url: &str) -> Result<(), Error> {
|
||||||
|
self.rt.block_on(self.api.add_tracker(hash, url))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn get_trackers(&self, hash: &str) -> Result<Vec<TorrentTracker>, Error> {
|
||||||
|
self.rt.block_on(self.api.get_trackers(hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn remove_tracker(&self, hash: &str, url: &str) -> Result<(), Error> {
|
||||||
|
self.rt.block_on(self.api.remove_tracker(hash, url))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
use snafu::prelude::*;
|
use snafu::prelude::*;
|
||||||
|
use tokio::runtime::{Builder,Runtime};
|
||||||
|
|
||||||
use imdl::infohash::Infohash;
|
use imdl::infohash::Infohash;
|
||||||
use imdl::input::Input;
|
use imdl::input::Input;
|
||||||
|
@ -84,3 +85,9 @@ pub fn find_free_port() -> u16 {
|
||||||
let bind = TcpListener::bind("127.0.0.1:0").unwrap();
|
let bind = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
bind.local_addr().unwrap().port()
|
bind.local_addr().unwrap().port()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn blocking_runtime() -> std::io::Result<Runtime> {
|
||||||
|
Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user