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 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 let resp = request.send().await?;
86 log::debug!("response: {:?}", resp);
87
88 if let Some(error) = endpoint.check_status(resp.status()) {
90 return Err(error);
91 }
92 let de_resp = endpoint.de_response(resp).await?;
94 Ok(de_resp)
95 }
96
97 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}