qbit_api_rs/
client.rs

1use crate::error::ClientError;
2use crate::{
3    api::{self, Endpoint},
4    types,
5};
6use reqwest::Client;
7use reqwest_cookie_store::{CookieStore, CookieStoreMutex};
8use std::{error::Error, io::prelude::*, path::Path, sync::Arc};
9use url::Url;
10
11#[derive(Debug, Clone)]
12pub struct Credential {
13    pub username: String,
14    pub password: String,
15}
16
17#[derive(Debug)]
18pub struct QbitClient {
19    pub host: Url,
20    pub auth: Credential,
21    pub client: Client,
22    pub cookie_store: Arc<CookieStoreMutex>,
23}
24
25impl QbitClient {
26    fn _try_new(host: &str, username: &str, password: &str) -> Result<Self, ClientError> {
27        let cookie_store = Arc::new(CookieStoreMutex::new(CookieStore::new(None)));
28        let client = Client::builder()
29            .cookie_provider(cookie_store.clone())
30            .build()
31            .map_err(|e| ClientError::Initialize(e.to_string()))?;
32        Ok(Self {
33            host: Url::parse(host).map_err(|e| ClientError::Initialize(e.to_string()))?,
34            auth: Credential {
35                username: username.to_owned(),
36                password: password.to_owned(),
37            },
38            client,
39            cookie_store,
40        })
41    }
42    pub fn new_with_user_pwd<U>(host: U, username: U, password: U) -> Result<Self, ClientError>
43    where
44        U: AsRef<str>,
45    {
46        Self::_try_new(host.as_ref(), username.as_ref(), password.as_ref())
47    }
48
49    pub fn new_from_env() -> Result<Self, ClientError> {
50        use std::env::var;
51
52        let host =
53            var("QBIT_HOST").map_err(|e| ClientError::Initialize(format!("`QBIT_HOST` {}", e)))?;
54        let username = var("QBIT_USERNAME")
55            .map_err(|e| ClientError::Initialize(format!("`QBIT_USERNAME` {}", e)))?;
56        let password = var("QBIT_PASSWORD")
57            .map_err(|e| ClientError::Initialize(format!("`QBIT_PASSWORD` {}", e)))?;
58        Self::_try_new(&host, &username, &password)
59    }
60
61    pub async fn _resp<E>(&self, endpoint: &E) -> Result<E::Response, ClientError>
62    where
63        E: Endpoint,
64    {
65        let url = self.host.join(&endpoint.relative_path())?;
66        let mut request = self.client.request(endpoint.method(), url);
67
68        // build Headers
69        let mut headers = reqwest::header::HeaderMap::new();
70        headers.insert("Referer", self.host.to_string().parse()?);
71        request = request.headers(headers);
72
73        if let Some(query) = endpoint.query() {
74            request = request.query(query);
75        }
76        if let Some(form) = endpoint.form() {
77            request = request.form(form);
78        }
79        if let Some(multipart) = endpoint.multipart() {
80            request = request.multipart(multipart);
81        }
82        log::debug!("request: {:?}", request);
83
84        // send request
85        let resp = request.send().await?;
86        log::debug!("response: {:?}", resp);
87
88        // check status code, return errors that defined in api
89        if let Some(error) = endpoint.check_status(resp.status()) {
90            return Err(error);
91        }
92        // deserialize response as string or type defined in api
93        let de_resp = endpoint.de_response(resp).await?;
94        Ok(de_resp)
95    }
96
97    /// # `/api/v2/auth/login`
98    pub async fn auth_login(&self) -> Result<(), ClientError> {
99        let auth_form = types::auth::LoginForm {
100            username: self.auth.username.clone(),
101            password: self.auth.password.clone(),
102        };
103        let api_auth_login = api::auth::Login { f: auth_form };
104
105        {
106            let mut store = self.cookie_store.lock().unwrap();
107            store.clear();
108        }
109
110        let _s = self._resp(&api_auth_login).await?;
111
112        Ok(())
113    }
114
115    /// # `/api/v2/auth/logout`
116    pub async fn auth_logout(&self) -> Result<(), ClientError> {
117        let api_auth_logout = api::auth::Logout {};
118        let _s = self._resp(&api_auth_logout).await?;
119
120        Ok(())
121    }
122
123    /// # `/api/v2/app/version`
124    pub async fn app_version(&self) -> Result<String, ClientError> {
125        let api_app_version = api::app::Version {};
126        let s = self._resp(&api_app_version).await?;
127
128        Ok(s)
129    }
130
131    /// # `/api/v2/app/webapiVersion`
132    pub async fn app_webapi_version(&self) -> Result<String, ClientError> {
133        let api_app_webapi_version = api::app::WebApiVersion {};
134        let s = self._resp(&api_app_webapi_version).await?;
135
136        Ok(s)
137    }
138
139    /// # `/api/v2/app/buildInfo`
140    pub async fn app_build_info(&self) -> Result<types::app::BuildInfoResponse, ClientError> {
141        let api_build_info = api::app::BuildInfo {};
142        let de_resp = self._resp(&api_build_info).await?;
143
144        Ok(de_resp)
145    }
146
147    /// # `/api/v2/app/shutdown`
148    pub async fn app_shutdown(&self) -> Result<(), ClientError> {
149        let api_app_shutdown = api::app::Shutdown {};
150        let _s = self._resp(&api_app_shutdown).await?;
151
152        Ok(())
153    }
154
155    /// # `/api/v2/app/preferences`
156    pub async fn app_preferences(&self) -> Result<types::app::Preferences, ClientError> {
157        let api_app_preferences = api::app::Preferences {};
158        let de_resp = self._resp(&api_app_preferences).await?;
159
160        Ok(de_resp)
161    }
162
163    /// # `/api/v2/app/setPreferences`
164    pub async fn app_set_preferences(
165        &self,
166        f: &types::app::SetPreferencesForm,
167    ) -> Result<(), ClientError> {
168        let api_set_preferences = api::app::SetPreferences { f: f.to_owned() };
169        let _s = self._resp(&api_set_preferences).await?;
170
171        Ok(())
172    }
173
174    /// # `/api/v2/app/defaultSavePath`
175    pub async fn app_default_save_path(&self) -> Result<(), ClientError> {
176        let api_default_save_path = api::app::DefaultSavePath {};
177        let _s = self._resp(&api_default_save_path).await?;
178
179        Ok(())
180    }
181
182    /// # `/api/v2/log/main`
183    pub async fn log_main(
184        &self,
185        q: &types::log::MainQuery,
186    ) -> Result<Vec<types::log::MainResponseItem>, ClientError> {
187        let api_logmain = api::log::Main { q: q.to_owned() };
188        let de_resp = self._resp(&api_logmain).await?;
189
190        Ok(de_resp)
191    }
192
193    /// # `/api/v2/log/peers`
194    pub async fn log_peers(
195        &self,
196        q: &types::log::PeersQuery,
197    ) -> Result<Vec<types::log::PeersResponseItem>, ClientError> {
198        let api_logpeers = api::log::Peers { q: q.to_owned() };
199        let de_resp = self._resp(&api_logpeers).await?;
200
201        Ok(de_resp)
202    }
203
204    /// # `/api/v2/sync/maindata`
205    pub async fn sync_maindata(
206        &self,
207        q: &types::sync::MaindataQuery,
208    ) -> Result<types::sync::MaindataResponse, ClientError> {
209        let api_maindata = api::sync::Maindata { q: q.to_owned() };
210        let de_resp = self._resp(&api_maindata).await?;
211
212        Ok(de_resp)
213    }
214
215    /// # `/api/v2/sync/torrentPeers`
216    pub async fn sync_torrent_peers(
217        &self,
218        q: &types::sync::TorrentPeersQuery,
219    ) -> Result<types::sync::TorrentPeersResponse, ClientError> {
220        let api_torrent_peers = api::sync::TorrentPeers { q: q.to_owned() };
221        let de_resp = self._resp(&api_torrent_peers).await?;
222
223        Ok(de_resp)
224    }
225
226    /// # `/api/v2/transfer/info`
227    pub async fn transfer_info(&self) -> Result<types::transfer::InfoResponse, ClientError> {
228        let api_transfer_info = api::transfer::Info {};
229        let de_resp = self._resp(&api_transfer_info).await?;
230
231        Ok(de_resp)
232    }
233
234    /// # `/api/v2/transfer/speedLimitsMode`
235    pub async fn speed_limits_mode(
236        &self,
237    ) -> Result<types::transfer::SpeedLimitsModeResponse, ClientError> {
238        let api_speed_limits_mode = api::transfer::SpeedLimitsMode {};
239        let de_resp = self._resp(&api_speed_limits_mode).await?;
240
241        Ok(de_resp)
242    }
243
244    /// # `/api/v2/transfer/toggleSpeedLimitsMode`
245    pub async fn toggle_speed_limits_mode(&self) -> Result<(), ClientError> {
246        let api_toggle_speed_limits_mode = api::transfer::ToggleSpeedLimitsMode {};
247        let _s = self._resp(&api_toggle_speed_limits_mode).await?;
248
249        Ok(())
250    }
251
252    /// # `/api/v2/transfer/downloadLimit`
253    pub async fn download_limit(&self) -> Result<u64, ClientError> {
254        let api_download_limit = api::transfer::DownloadLimit {};
255        let s = self._resp(&api_download_limit).await?;
256        let dl_speed: u64 = s.parse().map_err(|_e| ClientError::ParseError)?;
257        Ok(dl_speed)
258    }
259
260    /// # `/api/v2/transfer/setDownloadLimit`
261    pub async fn set_download_limit(&self, limit: u64) -> Result<(), ClientError> {
262        let api_set_download_limit = api::transfer::SetDownloadLimit {
263            f: types::transfer::SetDownloadLimitForm { limit },
264        };
265        let _s = self._resp(&api_set_download_limit).await?;
266
267        Ok(())
268    }
269
270    /// # `/api/v2/transfer/uploadLimit`
271    pub async fn upload_limit(&self) -> Result<u64, ClientError> {
272        let api_upload_limit = api::transfer::UploadLimit {};
273        let s = self._resp(&api_upload_limit).await?;
274        let ul_speed: u64 = s.parse().map_err(|_e| ClientError::ParseError)?;
275        Ok(ul_speed)
276    }
277
278    /// # `/api/v2/transfer/setUploadLimit`
279    pub async fn set_upload_limit(&self, limit: u64) -> Result<(), ClientError> {
280        let api_set_upload_limit = api::transfer::SetUploadLimit {
281            f: types::transfer::SetUploadLimitForm { limit },
282        };
283        let _s = self._resp(&api_set_upload_limit).await?;
284
285        Ok(())
286    }
287
288    /// # `/api/v2/transfer/banPeers`
289    pub async fn ban_peers<T>(&self, peers: &[T]) -> Result<(), ClientError>
290    where
291        T: AsRef<str>,
292    {
293        let peers: Vec<String> = peers.iter().map(|p| p.as_ref().to_owned()).collect();
294
295        let f = types::transfer::BanPeersForm { peers };
296        let api_ban_peers = api::transfer::BanPeers { f };
297        let _s = self._resp(&api_ban_peers).await?;
298
299        Ok(())
300    }
301
302    /// # `/api/v2/torrents/info`
303    pub async fn torrents_info(
304        &self,
305        q: &types::torrents::InfoQuery,
306    ) -> Result<types::torrents::InfoResponse, ClientError> {
307        let api_torrents_info = api::torrents::Info { q: q.to_owned() };
308        let de_resp = self._resp(&api_torrents_info).await?;
309
310        Ok(de_resp)
311    }
312
313    /// # `/api/v2/torrents/properties`
314    pub async fn torrents_properties(
315        &self,
316        hash: &str,
317    ) -> Result<types::torrents::PropertiesResponse, ClientError> {
318        let q = types::torrents::PropertiesQuery {
319            hash: hash.to_owned(),
320        };
321        let api_torrents_properties = api::torrents::Properties { q };
322        let de_resp = self._resp(&api_torrents_properties).await?;
323
324        Ok(de_resp)
325    }
326
327    /// # `/api/v2/torrents/trackers`
328    pub async fn torrents_trackers(
329        &self,
330        hash: &str,
331    ) -> Result<types::torrents::TrackersResponse, ClientError> {
332        let q = types::torrents::TrackersQuery {
333            hash: hash.to_owned(),
334        };
335        let api_torrents_trackers = api::torrents::Trackers { q };
336        let de_resp = self._resp(&api_torrents_trackers).await?;
337
338        Ok(de_resp)
339    }
340
341    /// # `/api/v2/torrents/webseeds`
342    pub async fn torrents_webseeds(
343        &self,
344        hash: &str,
345    ) -> Result<types::torrents::WebseedsResponse, ClientError> {
346        let q = types::torrents::WebseedsQuery {
347            hash: hash.to_owned(),
348        };
349        let api_torrents_webseeds = api::torrents::Webseeds { q };
350        let de_resp = self._resp(&api_torrents_webseeds).await?;
351
352        Ok(de_resp)
353    }
354
355    /// # `/api/v2/torrents/files`
356    pub async fn torrents_files(
357        &self,
358        hash: &str,
359        indexes: Option<&[u64]>,
360    ) -> Result<types::torrents::FilesResponse, ClientError> {
361        let indexes = indexes.map(|v| {
362            v.iter()
363                .map(|i| i.to_string())
364                .collect::<Vec<String>>()
365                .join("|")
366        });
367
368        let q = types::torrents::FilesQuery {
369            hash: hash.to_owned(),
370            indexes,
371        };
372        let api_torrents_files = api::torrents::Files { q };
373        let de_resp = self._resp(&api_torrents_files).await?;
374
375        Ok(de_resp)
376    }
377
378    /// # `/api/v2/torrents/pieceStates`
379    pub async fn torrents_piece_states(
380        &self,
381        hash: &str,
382    ) -> Result<types::torrents::PieceStatesResponse, ClientError> {
383        let q = types::torrents::PieceStatesQuery {
384            hash: hash.to_owned(),
385        };
386        let api_torrents_piece_states = api::torrents::PieceStates { q };
387        let de_resp = self._resp(&api_torrents_piece_states).await?;
388
389        Ok(de_resp)
390    }
391
392    /// # `/api/v2/torrents/pieceHashes`
393    pub async fn torrents_piece_hashes(
394        &self,
395        hash: &str,
396    ) -> Result<types::torrents::PieceHashesResponse, ClientError> {
397        let q = types::torrents::PieceHashesQuery {
398            hash: hash.to_owned(),
399        };
400        let api_torrents_piece_hashes = api::torrents::PieceHashes { q };
401        let de_resp = self._resp(&api_torrents_piece_hashes).await?;
402
403        Ok(de_resp)
404    }
405
406    /// # `/api/v2/torrents/pause`
407    pub async fn torrents_pause<T>(&self, hashes: &[T]) -> Result<(), ClientError>
408    where
409        T: AsRef<str>,
410    {
411        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
412
413        let f = types::torrents::PauseForm { hashes };
414        let api_torrents_pause = api::torrents::Pause { f };
415        let _s = self._resp(&api_torrents_pause).await?;
416
417        Ok(())
418    }
419
420    /// # `/api/v2/torrents/resume`
421    pub async fn torrents_resume<T>(&self, hashes: &[T]) -> Result<(), ClientError>
422    where
423        T: AsRef<str>,
424    {
425        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
426
427        let f = types::torrents::ResumeForm { hashes };
428        let api_torrents_resume = api::torrents::Resume { f };
429        let _s = self._resp(&api_torrents_resume).await?;
430
431        Ok(())
432    }
433
434    /// # `/api/v2/torrents/delete`
435    pub async fn torrents_delete<T>(
436        &self,
437        hashes: &[T],
438        delete_files: bool,
439    ) -> Result<(), ClientError>
440    where
441        T: AsRef<str>,
442    {
443        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
444
445        let f = types::torrents::DeleteForm {
446            hashes,
447            delete_files,
448        };
449        let api_torrents_delete = api::torrents::Delete { f };
450        let _s = self._resp(&api_torrents_delete).await?;
451
452        Ok(())
453    }
454
455    /// # `/api/v2/torrents/recheck`
456    pub async fn torrents_recheck<H>(&self, hashes: &[H]) -> Result<(), ClientError>
457    where
458        H: AsRef<str>,
459    {
460        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
461
462        let f = types::torrents::RecheckForm { hashes };
463        let api_torrents_recheck = api::torrents::Recheck { f };
464        let _s = self._resp(&api_torrents_recheck).await?;
465
466        Ok(())
467    }
468
469    /// # `/api/v2/torrents/reannounce`
470    pub async fn torrents_reannounce<H>(&self, hashes: &[H]) -> Result<(), ClientError>
471    where
472        H: AsRef<str>,
473    {
474        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
475
476        let f = types::torrents::ReannounceForm { hashes };
477        let api_torrents_reannounce = api::torrents::Reannounce { f };
478        let _s = self._resp(&api_torrents_reannounce).await?;
479
480        Ok(())
481    }
482
483    /// # `/api/v2/torrents/add`
484    pub async fn torrents_add_by_url<U>(&self, urls: &[U]) -> Result<(), ClientError>
485    where
486        U: AsRef<str>,
487    {
488        let urls: Vec<String> = urls.iter().map(|u| u.as_ref().to_owned()).collect();
489        let ta = types::torrents::AddMultipart {
490            urls,
491            torrents: vec![],
492            ..Default::default()
493        };
494        self.torrents_add(ta).await?;
495
496        Ok(())
497    }
498
499    /// # `/api/v2/torrents/add`
500    pub async fn torrents_add_by_file<F>(&self, files: &[F]) -> Result<(), ClientError>
501    where
502        F: AsRef<Path>,
503    {
504        type VecOfNameAndContent = Vec<(String, Vec<u8>)>;
505        let fc = |x: &F| -> Result<(String, Vec<u8>), Box<dyn Error>> {
506            let mut f = std::fs::File::open(x.as_ref())?;
507            let mut buffer = Vec::new();
508            f.read_to_end(&mut buffer)?;
509            Ok((
510                x.as_ref()
511                    .file_name()
512                    .ok_or("no file name")?
513                    .to_string_lossy()
514                    .to_string(),
515                buffer,
516            ))
517        };
518        let files: Result<VecOfNameAndContent, Box<dyn Error>> = files.iter().map(fc).collect();
519        let files = files.map_err(|_| ClientError::Other("".into()))?;
520        let ta = types::torrents::AddMultipart {
521            urls: vec![],
522            torrents: files,
523            ..Default::default()
524        };
525        self.torrents_add(ta).await?;
526
527        Ok(())
528    }
529
530    /// # `/api/v2/torrents/add`
531    async fn torrents_add(&self, ta: types::torrents::AddMultipart) -> Result<(), ClientError> {
532        let api_torrents_add = api::torrents::Add { mp: ta };
533        if api_torrents_add.multipart().is_none() {
534            return Err(ClientError::InvalidMultipart("no valid multipart".into()));
535        }
536        let _s = self._resp(&api_torrents_add).await?;
537
538        Ok(())
539    }
540
541    /// # `/api/v2/torrents/addTrackers`
542    pub async fn torrents_add_trackers<H, U>(&self, hash: H, urls: &[U]) -> Result<(), ClientError>
543    where
544        H: AsRef<str>,
545        U: AsRef<str>,
546    {
547        let urls: Vec<String> = urls.iter().map(|u| u.as_ref().to_owned()).collect();
548
549        let f = types::torrents::AddTrackersForm {
550            hash: hash.as_ref().to_owned(),
551            urls,
552        };
553        let api_torrents_add_trackers = api::torrents::AddTrackers { f };
554        let _s = self._resp(&api_torrents_add_trackers).await?;
555
556        Ok(())
557    }
558
559    /// # `/api/v2/torrents/editTracker`
560    pub async fn torrents_edit_tracker<H, U>(
561        &self,
562        hash: H,
563        orig_url: U,
564        new_url: U,
565    ) -> Result<(), ClientError>
566    where
567        H: AsRef<str>,
568        U: AsRef<str>,
569    {
570        let f = types::torrents::EditTrackerForm {
571            hash: hash.as_ref().to_owned(),
572            orig_url: orig_url.as_ref().to_owned(),
573            new_url: new_url.as_ref().to_owned(),
574        };
575        let api_torrents_edit_tracker = api::torrents::EditTracker { f };
576        let _s = self._resp(&api_torrents_edit_tracker).await?;
577
578        Ok(())
579    }
580
581    /// # `/api/v2/torrents/removeTrackers`
582    pub async fn torrents_remove_trackers<H, U>(
583        &self,
584        hash: H,
585        urls: &[U],
586    ) -> Result<(), ClientError>
587    where
588        H: AsRef<str>,
589        U: AsRef<str>,
590    {
591        let urls: Vec<String> = urls.iter().map(|u| u.as_ref().to_owned()).collect();
592
593        let f = types::torrents::RemoveTrackersForm {
594            hash: hash.as_ref().to_owned(),
595            urls,
596        };
597        let api_torrents_remove_trackers = api::torrents::RemoveTrackers { f };
598        let _s = self._resp(&api_torrents_remove_trackers).await?;
599
600        Ok(())
601    }
602
603    /// # `/api/v2/torrents/addPeers`
604    pub async fn torrents_add_peers<H, P>(
605        &self,
606        hashes: &[H],
607        peers: &[P],
608    ) -> Result<(), ClientError>
609    where
610        H: AsRef<str>,
611        P: AsRef<str>,
612    {
613        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
614        let peers: Vec<String> = peers.iter().map(|p| p.as_ref().to_owned()).collect();
615
616        let f = types::torrents::AddPeersForm { hashes, peers };
617        let api_torrents_add_peers = api::torrents::AddPeers { f };
618        let _s = self._resp(&api_torrents_add_peers).await.unwrap();
619
620        Ok(())
621    }
622
623    /// # `/api/v2/torrents/increasePrio`
624    pub async fn torrents_increase_prio<H>(&self, hashes: &[H]) -> Result<(), ClientError>
625    where
626        H: AsRef<str>,
627    {
628        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
629
630        let f = types::torrents::IncreasePrioForm { hashes };
631        let api_torrents_increase_prio = api::torrents::IncreasePrio { f };
632        let _s = self._resp(&api_torrents_increase_prio).await.unwrap();
633
634        Ok(())
635    }
636
637    /// # `/api/v2/torrents/decreasePrio`
638    pub async fn torrents_decrease_prio<H>(&self, hashes: &[H]) -> Result<(), ClientError>
639    where
640        H: AsRef<str>,
641    {
642        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
643
644        let f = types::torrents::DecreasePrioForm { hashes };
645        let api_torrents_decrease_prio = api::torrents::DecreasePrio { f };
646        let _s = self._resp(&api_torrents_decrease_prio).await.unwrap();
647
648        Ok(())
649    }
650
651    /// # `/api/v2/torrents/topPrio`
652    pub async fn torrents_top_prio<H>(&self, hashes: &[H]) -> Result<(), ClientError>
653    where
654        H: AsRef<str>,
655    {
656        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
657
658        let f = types::torrents::TopPrioForm { hashes };
659        let api_torrents_top_prio = api::torrents::TopPrio { f };
660        let _s = self._resp(&api_torrents_top_prio).await.unwrap();
661
662        Ok(())
663    }
664
665    /// # `/api/v2/torrents/bottomPrio`
666    pub async fn torrents_bottom_prio<H>(&self, hashes: &[H]) -> Result<(), ClientError>
667    where
668        H: AsRef<str>,
669    {
670        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
671
672        let f = types::torrents::BottomPrioForm { hashes };
673        let api_torrents_bottom_prio = api::torrents::BottomPrio { f };
674        let _s = self._resp(&api_torrents_bottom_prio).await.unwrap();
675
676        Ok(())
677    }
678
679    /// # `/api/v2/torrents/filePrio`
680    // TODO: Implement
681
682    /// # `/api/v2/torrents/downloadLimit`
683    pub async fn torrents_download_limit<H>(
684        &self,
685        hashes: &[H],
686    ) -> Result<types::torrents::DownloadLimitResponse, ClientError>
687    where
688        H: AsRef<str>,
689    {
690        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
691
692        let f = types::torrents::DownloadLimitForm { hashes };
693        let api_torrents_download_limit = api::torrents::DownloadLimit { f };
694        let de_resp = self._resp(&api_torrents_download_limit).await.unwrap();
695
696        Ok(de_resp)
697    }
698
699    /// # `/api/v2/torrents/setDownloadLimit`
700    pub async fn torrents_set_download_limit<H>(
701        &self,
702        hashes: &[H],
703        limit: u64,
704    ) -> Result<(), ClientError>
705    where
706        H: AsRef<str>,
707    {
708        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
709
710        let f = types::torrents::SetDownloadLimitForm { hashes, limit };
711        let api_torrents_set_download_limit = api::torrents::SetDownloadLimit { f };
712        let _s = self._resp(&api_torrents_set_download_limit).await.unwrap();
713
714        Ok(())
715    }
716
717    /// # `/api/v2/torrents/setShareLimits`
718    pub async fn torrents_set_share_limits<H>(
719        &self,
720        hashes: &[H],
721        ratio_limit: types::torrents::RatioLimit,
722        seeding_time_limit: i64,
723    ) -> Result<(), ClientError>
724    where
725        H: AsRef<str>,
726    {
727        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
728
729        let f = types::torrents::SetShareLimitsForm {
730            hashes,
731            ratio_limit,
732            seeding_time_limit,
733        };
734        let api_torrents_set_share_limits = api::torrents::SetShareLimits { f };
735        let _s = self._resp(&api_torrents_set_share_limits).await.unwrap();
736
737        Ok(())
738    }
739
740    /// # `/api/v2/torrents/uploadLimit`
741    pub async fn torrents_upload_limit<H>(
742        &self,
743        hashes: &[H],
744    ) -> Result<types::torrents::UploadLimitResponse, ClientError>
745    where
746        H: AsRef<str>,
747    {
748        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
749
750        let f = types::torrents::UploadLimitForm { hashes };
751        let api_torrents_upload_limit = api::torrents::UploadLimit { f };
752        let de_resp = self._resp(&api_torrents_upload_limit).await.unwrap();
753
754        Ok(de_resp)
755    }
756
757    /// # `/api/v2/torrents/setUploadLimit`
758    pub async fn torrents_set_upload_limit<H>(
759        &self,
760        hashes: &[H],
761        limit: u64,
762    ) -> Result<(), ClientError>
763    where
764        H: AsRef<str>,
765    {
766        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
767
768        let f = types::torrents::SetUploadLimitForm { hashes, limit };
769        let api_torrents_set_upload_limit = api::torrents::SetUploadLimit { f };
770        let _s = self._resp(&api_torrents_set_upload_limit).await.unwrap();
771
772        Ok(())
773    }
774
775    /// # `/api/v2/torrents/setLocation`
776    pub async fn torrents_set_location<H, L>(
777        &self,
778        hashes: &[H],
779        location: L,
780    ) -> Result<(), ClientError>
781    where
782        H: AsRef<str>,
783        L: AsRef<Path>,
784    {
785        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
786
787        let f = types::torrents::SetLocationForm {
788            hashes,
789            location: location.as_ref().to_string_lossy().to_string(),
790        };
791        let api_torrents_set_location = api::torrents::SetLocation { f };
792        let _s = self._resp(&api_torrents_set_location).await.unwrap();
793
794        Ok(())
795    }
796
797    /// # `/api/v2/torrents/rename`
798    pub async fn torernts_rename<H, N>(&self, hash: H, name: N) -> Result<(), ClientError>
799    where
800        H: AsRef<str>,
801        N: AsRef<str>,
802    {
803        let f = types::torrents::RenameForm {
804            hash: hash.as_ref().to_owned(),
805            name: name.as_ref().to_owned(),
806        };
807        let api_torrents_rename = api::torrents::Rename { f };
808        let _s = self._resp(&api_torrents_rename).await.unwrap();
809
810        Ok(())
811    }
812
813    /// # `/api/v2/torrents/setCategory`
814    pub async fn torernts_set_category<H, C>(
815        &self,
816        hashes: &[H],
817        category: C,
818    ) -> Result<(), ClientError>
819    where
820        H: AsRef<str>,
821        C: AsRef<str>,
822    {
823        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
824
825        let f = types::torrents::SetCategoryForm {
826            hashes,
827            category: category.as_ref().to_owned(),
828        };
829        let api_torrents_set_category = api::torrents::SetCategory { f };
830        let _s = self._resp(&api_torrents_set_category).await.unwrap();
831
832        Ok(())
833    }
834
835    /// # `/api/v2/torrents/categories`
836    pub async fn torrents_categories(
837        &self,
838    ) -> Result<types::torrents::CategoriesResponse, ClientError> {
839        let api_torrents_categories = api::torrents::Categories {};
840        let de_resp = self._resp(&api_torrents_categories).await.unwrap();
841
842        Ok(de_resp)
843    }
844
845    /// # `/api/v2/torrents/createCategory`
846    pub async fn torrents_create_category<C, P>(
847        &self,
848        category: C,
849        save_path: P,
850    ) -> Result<(), ClientError>
851    where
852        C: AsRef<str>,
853        P: AsRef<Path>,
854    {
855        let f = types::torrents::CreateCategoryForm {
856            category: category.as_ref().to_owned(),
857            save_path: save_path.as_ref().to_string_lossy().to_string(),
858        };
859        let api_torrents_create_category = api::torrents::CreateCategory { f };
860        let _s = self._resp(&api_torrents_create_category).await.unwrap();
861
862        Ok(())
863    }
864
865    /// # `/api/v2/torrents/editCategory`
866    pub async fn torrents_edit_category<C, P>(
867        &self,
868        category: C,
869        save_path: P,
870    ) -> Result<(), ClientError>
871    where
872        C: AsRef<str>,
873        P: AsRef<Path>,
874    {
875        let f = types::torrents::EditCategoryForm {
876            category: category.as_ref().to_owned(),
877            save_path: save_path.as_ref().to_string_lossy().to_string(),
878        };
879        let api_torrents_edit_category = api::torrents::EditCategory { f };
880        let _s = self._resp(&api_torrents_edit_category).await.unwrap();
881
882        Ok(())
883    }
884
885    /// # `/api/v2/torrents/removeCategories`
886    pub async fn torrents_remove_categories<C>(&self, categories: &[C]) -> Result<(), ClientError>
887    where
888        C: AsRef<str>,
889    {
890        let categories: Vec<String> = categories.iter().map(|c| c.as_ref().to_owned()).collect();
891
892        let f = types::torrents::RemoveCategoriesForm { categories };
893        let api_torrents_remove_categories = api::torrents::RemoveCategories { f };
894        let _s = self._resp(&api_torrents_remove_categories).await.unwrap();
895
896        Ok(())
897    }
898
899    /// # `/api/v2/torrents/addTags`
900    pub async fn torrents_add_tags<H, T>(&self, hashes: &[H], tags: &[T]) -> Result<(), ClientError>
901    where
902        H: AsRef<str>,
903        T: AsRef<str>,
904    {
905        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
906        let tags: Vec<String> = tags.iter().map(|t| t.as_ref().to_owned()).collect();
907
908        let f = types::torrents::AddTagsForm { hashes, tags };
909        let api_torrents_add_tags = api::torrents::AddTags { f };
910        let _s = self._resp(&api_torrents_add_tags).await.unwrap();
911
912        Ok(())
913    }
914
915    /// # `/api/v2/torrents/removeTags`
916    pub async fn torrents_remove_tags<H, T>(
917        &self,
918        hashes: &[H],
919        tags: &[T],
920    ) -> Result<(), ClientError>
921    where
922        H: AsRef<str>,
923        T: AsRef<str>,
924    {
925        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
926        let tags: Vec<String> = tags.iter().map(|t| t.as_ref().to_owned()).collect();
927
928        let f = types::torrents::RemoveTagsForm { hashes, tags };
929        let api_torrents_remove_tags = api::torrents::RemoveTags { f };
930        let _s = self._resp(&api_torrents_remove_tags).await.unwrap();
931
932        Ok(())
933    }
934
935    /// # `/api/v2/torrents/tags`
936    pub async fn torrents_tags(&self) -> Result<types::torrents::TagsResponse, ClientError> {
937        let api_torrents_tags = api::torrents::Tags {};
938        let de_resp = self._resp(&api_torrents_tags).await.unwrap();
939
940        Ok(de_resp)
941    }
942
943    /// # `/api/v2/torrents/createTags`
944    pub async fn torrens_create_tags<T>(&self, tags: &[T]) -> Result<(), ClientError>
945    where
946        T: AsRef<str>,
947    {
948        let tags: Vec<String> = tags.iter().map(|t| t.as_ref().to_owned()).collect();
949
950        let f = types::torrents::CreateTagsForm { tags };
951        let api_torrents_create_tags = api::torrents::CreateTags { f };
952        let _s = self._resp(&api_torrents_create_tags).await.unwrap();
953
954        Ok(())
955    }
956
957    /// # `/api/v2/torrents/deleteTags`
958    pub async fn torrents_delete_tags<T>(&self, tags: &[T]) -> Result<(), ClientError>
959    where
960        T: AsRef<str>,
961    {
962        let tags: Vec<String> = tags.iter().map(|t| t.as_ref().to_owned()).collect();
963
964        let f = types::torrents::DeleteTagsForm { tags };
965        let api_torrents_delete_tags = api::torrents::DeleteTags { f };
966        let _s = self._resp(&api_torrents_delete_tags).await.unwrap();
967
968        Ok(())
969    }
970
971    /// # `/api/v2/torrents/setAutoManagement`
972    pub async fn torrents_set_auto_management<H>(
973        &self,
974        hashes: &[H],
975        enable: bool,
976    ) -> Result<(), ClientError>
977    where
978        H: AsRef<str>,
979    {
980        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
981
982        let f = types::torrents::SetAutoManagementForm { hashes, enable };
983        let api_torrents_set_automanagement = api::torrents::SetAutoManagement { f };
984        let _s = self._resp(&api_torrents_set_automanagement).await.unwrap();
985
986        Ok(())
987    }
988
989    /// # `/api/v2/torrents/toggleSequentialDownload`
990    pub async fn torrents_toggle_sequential_download<H>(
991        &self,
992        hashes: &[H],
993    ) -> Result<(), ClientError>
994    where
995        H: AsRef<str>,
996    {
997        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
998
999        let f = types::torrents::ToggleSequentialDownloadForm { hashes };
1000        let api_torrents_toggle_sequential_download = api::torrents::ToggleSequentialDownload { f };
1001        let _s = self
1002            ._resp(&api_torrents_toggle_sequential_download)
1003            .await
1004            .unwrap();
1005
1006        Ok(())
1007    }
1008
1009    /// # `/api/v2/torrents/toggleFirstLastPiecePrio`
1010    pub async fn torrents_toggle_first_last_piece_prio<H>(
1011        &self,
1012        hashes: &[H],
1013    ) -> Result<(), ClientError>
1014    where
1015        H: AsRef<str>,
1016    {
1017        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
1018
1019        let f = types::torrents::ToggleFirstLastPiecePrioForm { hashes };
1020        let api_torrents_toggle_first_last_piece_prio =
1021            api::torrents::ToggleFirstLastPiecePrio { f };
1022        let _s = self
1023            ._resp(&api_torrents_toggle_first_last_piece_prio)
1024            .await
1025            .unwrap();
1026
1027        Ok(())
1028    }
1029
1030    /// # `/api/v2/torrents/setForceStart`
1031    pub async fn torrents_set_force_start<H>(
1032        &self,
1033        hashes: &[H],
1034        value: bool,
1035    ) -> Result<(), ClientError>
1036    where
1037        H: AsRef<str>,
1038    {
1039        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
1040
1041        let f = types::torrents::SetForceStartForm { hashes, value };
1042        let api_torrents_set_force_start = api::torrents::SetForceStart { f };
1043        let _s = self._resp(&api_torrents_set_force_start).await.unwrap();
1044
1045        Ok(())
1046    }
1047
1048    /// # `/api/v2/torrents/setSuperSeeding`
1049    pub async fn torrents_set_super_seeding<H>(
1050        &self,
1051        hashes: &[H],
1052        value: bool,
1053    ) -> Result<(), ClientError>
1054    where
1055        H: AsRef<str>,
1056    {
1057        let hashes: Vec<String> = hashes.iter().map(|h| h.as_ref().to_owned()).collect();
1058
1059        let f = types::torrents::SetSuperSeedingForm { hashes, value };
1060        let api_torrents_set_super_seeding = api::torrents::SetSuperSeeding { f };
1061        let _s = self._resp(&api_torrents_set_super_seeding).await.unwrap();
1062
1063        Ok(())
1064    }
1065
1066    /// # `/api/v2/torrents/renameFile`
1067    pub async fn torrents_rename_file<H, P>(
1068        &self,
1069        hash: H,
1070        old_path: P,
1071        new_path: P,
1072    ) -> Result<(), ClientError>
1073    where
1074        H: AsRef<str>,
1075        P: AsRef<Path>,
1076    {
1077        let f = types::torrents::RenameFileForm {
1078            hash: hash.as_ref().to_owned(),
1079            old_path: old_path.as_ref().to_string_lossy().to_string(),
1080            new_path: new_path.as_ref().to_string_lossy().to_string(),
1081        };
1082        let api_torrents_rename_file = api::torrents::RenameFile { f };
1083        let _s = self._resp(&api_torrents_rename_file).await.unwrap();
1084
1085        Ok(())
1086    }
1087
1088    /// # `/api/v2/torrents/renameFolder`
1089    pub async fn torrents_rename_folder<H, P>(
1090        &self,
1091        hash: H,
1092        old_path: P,
1093        new_path: P,
1094    ) -> Result<(), ClientError>
1095    where
1096        H: AsRef<str>,
1097        P: AsRef<Path>,
1098    {
1099        let f = types::torrents::RenameFolderForm {
1100            hash: hash.as_ref().to_owned(),
1101            old_path: old_path.as_ref().to_string_lossy().to_string(),
1102            new_path: new_path.as_ref().to_string_lossy().to_string(),
1103        };
1104        let api_torrents_rename_folder = api::torrents::RenameFolder { f };
1105        let _s = self._resp(&api_torrents_rename_folder).await.unwrap();
1106
1107        Ok(())
1108    }
1109
1110    /// # `/api/v2/search/start`
1111    pub async fn search_start<T>(
1112        &self,
1113        pattern: T,
1114        plugins: T,
1115        category: T,
1116    ) -> Result<types::search::StartResponse, ClientError>
1117    where
1118        T: AsRef<str>,
1119    {
1120        let f = types::search::StartForm {
1121            pattern: pattern.as_ref().to_owned(),
1122            plugins: plugins.as_ref().to_owned(),
1123            category: category.as_ref().to_owned(),
1124        };
1125        let api_search_start = api::search::Start { f };
1126        let de_resp = self._resp(&api_search_start).await.unwrap();
1127
1128        Ok(de_resp)
1129    }
1130
1131    /// # `/api/v2/search/stop`
1132    pub async fn search_stop(&self, id: u64) -> Result<(), ClientError> {
1133        let f = types::search::StopForm { id };
1134        let api_search_stop = api::search::Stop { f };
1135        let _s = self._resp(&api_search_stop).await.unwrap();
1136
1137        Ok(())
1138    }
1139
1140    /// # `/api/v2/search/status`
1141    pub async fn search_status(
1142        &self,
1143        id: Option<u64>,
1144    ) -> Result<types::search::StatusResponse, ClientError> {
1145        let q = types::search::StatusQuery { id };
1146        let api_search_status = api::search::Status { q };
1147        let de_resp = self._resp(&api_search_status).await.unwrap();
1148
1149        Ok(de_resp)
1150    }
1151
1152    /// # `/api/v2/search/results`
1153    pub async fn search_results(
1154        &self,
1155        id: u64,
1156        limit: Option<i64>,
1157        offset: Option<i64>,
1158    ) -> Result<types::search::ResultsResponse, ClientError> {
1159        let q = types::search::ResultsQuery { id, limit, offset };
1160        let api_search_results = api::search::Results { q };
1161        let de_resp = self._resp(&api_search_results).await.unwrap();
1162
1163        Ok(de_resp)
1164    }
1165}