qbit_rs/model/
torrent.rs

1use std::fmt::{Debug, Display};
2
3use reqwest::Url;
4use serde::Serialize;
5use serde_with::{skip_serializing_none, SerializeDisplay};
6
7use crate::model::Sep;
8
9#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub enum TorrentFilter {
12    All,
13    Downloading,
14    Completed,
15    Paused,
16    Active,
17    Inactive,
18    Resumed,
19    Stalled,
20    StalledUploading,
21    StalledDownloading,
22    Errored,
23}
24
25#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
26pub struct Torrent {
27    /// Time (Unix Epoch) when the torrent was added to the client
28    pub added_on: Option<i64>,
29    /// Amount of data left to download (bytes)
30    pub amount_left: Option<i64>,
31    /// Whether this torrent is managed by Automatic Torrent Management
32    pub auto_tmm: Option<bool>,
33    /// Percentage of file pieces currently available
34    pub availability: Option<f64>,
35    /// Category of the torrent
36    pub category: Option<String>,
37    /// Amount of transfer data completed (bytes)
38    pub completed: Option<i64>,
39    /// Time (Unix Epoch) when the torrent completed
40    pub completion_on: Option<i64>,
41    /// Absolute path of torrent content (root path for multifile torrents,
42    /// absolute file path for singlefile torrents)
43    pub content_path: Option<String>,
44    /// Torrent download speed limit (bytes/s). `-1` if unlimited.
45    pub dl_limit: Option<i64>,
46    /// Torrent download speed (bytes/s)
47    pub dlspeed: Option<i64>,
48    /// Amount of data downloaded
49    pub downloaded: Option<i64>,
50    /// Amount of data downloaded this session
51    pub downloaded_session: Option<i64>,
52    /// Torrent ETA (seconds)
53    pub eta: Option<i64>,
54    /// True if first last piece are prioritized
55    pub f_l_piece_prio: Option<bool>,
56    /// True if force start is enabled for this torrent
57    pub force_start: Option<bool>,
58    /// Torrent hash
59    pub hash: Option<String>,
60    /// Last time (Unix Epoch) when a chunk was downloaded/uploaded
61    pub last_activity: Option<i64>,
62    /// Magnet URI corresponding to this torrent
63    pub magnet_uri: Option<String>,
64    /// Maximum share ratio until torrent is stopped from seeding/uploading
65    pub max_ratio: Option<f64>,
66    /// Maximum seeding time (seconds) until torrent is stopped from seeding
67    pub max_seeding_time: Option<i64>,
68    /// Torrent name
69    pub name: Option<String>,
70    /// Number of seeds in the swarm
71    pub num_complete: Option<i64>,
72    /// Number of leechers in the swarm
73    pub num_incomplete: Option<i64>,
74    /// Number of leechers connected to
75    pub num_leechs: Option<i64>,
76    /// Number of seeds connected to
77    pub num_seeds: Option<i64>,
78    /// Torrent priority. Returns -1 if queuing is disabled or torrent is in
79    /// seed mode
80    pub priority: Option<i64>,
81    /// Torrent progress (percentage/100)
82    pub progress: Option<f64>,
83    /// Torrent share ratio. Max ratio value: 9999.
84    pub ratio: Option<f64>,
85    pub ratio_limit: Option<f64>,
86    /// Path where this torrent's data is stored
87    pub save_path: Option<String>,
88    /// Torrent elapsed time while complete (seconds)
89    pub seeding_time: Option<i64>,
90    /// seeding_time_limit is a per torrent setting, when Automatic Torrent
91    /// Management is disabled, furthermore then max_seeding_time is set to
92    /// seeding_time_limit for this torrent. If Automatic Torrent Management
93    /// is enabled, the value is -2. And if max_seeding_time is unset it
94    /// have a default value -1.
95    pub seeding_time_limit: Option<i64>,
96    /// Time (Unix Epoch) when this torrent was last seen complete
97    pub seen_complete: Option<i64>,
98    /// True if sequential download is enabled
99    pub seq_dl: Option<bool>,
100    /// Total size (bytes) of files selected for download
101    pub size: Option<i64>,
102    /// Torrent state. See table here below for the possible values
103    pub state: Option<State>,
104    /// True if super seeding is enabled
105    pub super_seeding: Option<bool>,
106    /// Comma-concatenated tag list of the torrent
107    pub tags: Option<String>,
108    /// Total active time (seconds)
109    pub time_active: Option<i64>,
110    /// Total size (bytes) of all file in this torrent (including unselected
111    /// ones)
112    pub total_size: Option<i64>,
113    /// The first tracker with working status. Returns empty String if no
114    /// tracker is working.
115    pub tracker: Option<String>,
116    /// Torrent upload speed limit (bytes/s). `-1` if unlimited.
117    pub up_limit: Option<i64>,
118    /// Amount of data uploaded
119    pub uploaded: Option<i64>,
120    /// Amount of data uploaded this session
121    pub uploaded_session: Option<i64>,
122    /// Torrent upload speed (bytes/:,)
123    pub upspeed: Option<i64>,
124}
125
126#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
127pub enum State {
128    /// Some error occurred, applies to paused torrents
129    #[serde(rename = "error")]
130    Error,
131    /// Torrent data files is missing
132    #[serde(rename = "missingFiles")]
133    MissingFiles,
134    /// Torrent is being seeded and data is being transferred
135    #[serde(rename = "uploading")]
136    Uploading,
137    /// Torrent is paused and has finished downloading, 
138    /// stoppedUP is new name in qBit5+
139    #[serde(rename = "pausedUP", alias = "stoppedUP")]
140    PausedUP,
141    /// Queuing is enabled and torrent is queued for upload
142    #[serde(rename = "queuedUP")]
143    QueuedUP,
144    /// Torrent is being seeded, but no connection were made
145    #[serde(rename = "stalledUP")]
146    StalledUP,
147    /// Torrent has finished downloading and is being checked
148    #[serde(rename = "checkingUP")]
149    CheckingUP,
150    /// Torrent is forced to uploading and ignore queue limit
151    #[serde(rename = "forcedUP")]
152    ForcedUP,
153    /// Torrent is allocating disk space for download
154    #[serde(rename = "allocating")]
155    Allocating,
156    /// Torrent is being downloaded and data is being transferred
157    #[serde(rename = "downloading")]
158    Downloading,
159    /// Torrent has just started downloading and is fetching metadata
160    #[serde(rename = "metaDL")]
161    MetaDL,
162    /// Torrent is paused and has NOT finished downloading,
163    /// stoppedDL is new name in qBit5+
164    #[serde(rename = "pausedDL", alias = "stoppedDL")]
165    PausedDL,
166    /// Queuing is enabled and torrent is queued for download
167    #[serde(rename = "queuedDL")]
168    QueuedDL,
169    /// Torrent is being downloaded, but no connection were made
170    #[serde(rename = "stalledDL")]
171    StalledDL,
172    /// Same as checkingUP, but torrent has NOT finished downloading
173    #[serde(rename = "checkingDL")]
174    CheckingDL,
175    /// Torrent is forced to downloading to ignore queue limit
176    #[serde(rename = "forcedDL")]
177    ForcedDL,
178    /// Checking resume data on qBt startup
179    #[serde(rename = "checkingResumeData")]
180    CheckingResumeData,
181    /// Torrent is moving to another location
182    #[serde(rename = "moving")]
183    Moving,
184    /// Unknown status
185    #[serde(rename = "unknown")]
186    Unknown,
187}
188
189#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
190pub struct TorrentProperty {
191    /// Torrent save path
192    pub save_path: Option<String>,
193    /// Torrent creation date (Unix timestamp)
194    pub creation_date: Option<i64>,
195    /// Torrent piece size (bytes)
196    pub piece_size: Option<i64>,
197    /// Torrent comment
198    pub comment: Option<String>,
199    /// Total data wasted for torrent (bytes)
200    pub total_wasted: Option<i64>,
201    /// Total data uploaded for torrent (bytes)
202    pub total_uploaded: Option<i64>,
203    /// Total data uploaded this session (bytes)
204    pub total_uploaded_session: Option<i64>,
205    /// Total data downloaded for torrent (bytes)
206    pub total_downloaded: Option<i64>,
207    /// Total data downloaded this session (bytes)
208    pub total_downloaded_session: Option<i64>,
209    /// Torrent upload limit (bytes/s)
210    pub up_limit: Option<i64>,
211    /// Torrent download limit (bytes/s)
212    pub dl_limit: Option<i64>,
213    /// Torrent elapsed time (seconds)
214    pub time_elapsed: Option<i64>,
215    /// Torrent elapsed time while complete (seconds)
216    pub seeding_time: Option<i64>,
217    /// Torrent connection count
218    pub nb_connections: Option<i64>,
219    /// Torrent connection count limit
220    pub nb_connections_limit: Option<i64>,
221    /// Torrent share ratio
222    pub share_ratio: Option<f64>,
223    /// When this torrent was added (unix timestamp)
224    pub addition_date: Option<i64>,
225    /// Torrent completion date (unix timestamp)
226    pub completion_date: Option<i64>,
227    /// Torrent creator
228    pub created_by: Option<String>,
229    /// Torrent average download speed (bytes/second)
230    pub dl_speed_avg: Option<i64>,
231    /// Torrent download speed (bytes/second)
232    pub dl_speed: Option<i64>,
233    /// Torrent ETA (seconds)
234    pub eta: Option<i64>,
235    /// Last seen complete date (unix timestamp)
236    pub last_seen: Option<i64>,
237    /// Number of peers connected to
238    pub peers: Option<i64>,
239    /// Number of peers in the swarm
240    pub peers_total: Option<i64>,
241    /// Number of pieces owned
242    pub pieces_have: Option<i64>,
243    /// Number of pieces of the torrent
244    pub pieces_num: Option<i64>,
245    /// Number of seconds until the next announce
246    pub reannounce: Option<i64>,
247    /// Number of seeds connected to
248    pub seeds: Option<i64>,
249    /// Number of seeds in the swarm
250    pub seeds_total: Option<i64>,
251    /// Torrent total size (bytes)
252    pub total_size: Option<i64>,
253    /// Torrent average upload speed (bytes/second)
254    pub up_speed_avg: Option<i64>,
255    /// Torrent upload speed (bytes/second)
256    pub up_speed: Option<i64>,
257}
258
259#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
260pub struct WebSeed {
261    /// Web seed URL
262    pub url: Url,
263}
264
265#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
266pub struct TorrentContent {
267    /// File index
268    pub index: u64,
269    /// File name (including relative path),
270    pub name: String,
271    /// File size (bytes),
272    pub size: u64,
273    /// File progress (percentage/100),
274    pub progress: f64,
275    /// File priority. See possible values here below,
276    pub priority: Priority,
277    /// True if file is seeding/complete,
278    pub is_seed: Option<bool>,
279    /// The first number is the starting piece index and the second number is
280    /// the ending piece index (inclusive),
281    #[serde(default)]
282    pub piece_range: Vec<i64>,
283    /// Percentage of file pieces currently available (percentage/100),
284    #[serde(default)]
285    pub availability: f64,
286}
287
288#[derive(
289    Debug,
290    Clone,
291    Copy,
292    PartialEq,
293    Eq,
294    PartialOrd,
295    Ord,
296    serde_repr::Serialize_repr,
297    serde_repr::Deserialize_repr,
298)]
299#[repr(u8)]
300pub enum Priority {
301    /// Do not download
302    DoNotDownload = 0,
303    /// Normal priority
304    Normal        = 1,
305    /// Mixed
306    Mixed         = 4,
307    /// High priority
308    High          = 6,
309    /// Maximal priority
310    Maximal       = 7,
311}
312
313#[derive(
314    Debug,
315    Clone,
316    Copy,
317    PartialEq,
318    Eq,
319    PartialOrd,
320    Ord,
321    serde_repr::Serialize_repr,
322    serde_repr::Deserialize_repr,
323)]
324#[repr(u8)]
325pub enum PieceState {
326    /// Not downloaded yet
327    NotDownloaded = 0,
328    /// Now downloading
329    Downloading   = 1,
330    /// Already downloaded
331    Downloaded    = 2,
332}
333
334/// `|` separeated list of hash values or `all`
335#[derive(Debug, Clone, PartialEq, Eq, SerializeDisplay)]
336pub enum Hashes {
337    /// A list of torrent hashes separated by `|`
338    Hashes(Sep<String, '|'>),
339    /// All torrents
340    All,
341}
342
343impl<V: Into<Vec<String>>> From<V> for Hashes {
344    fn from(hashes: V) -> Self {
345        Hashes::Hashes(Sep::from(hashes))
346    }
347}
348
349impl Display for Hashes {
350    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
351        match self {
352            Hashes::Hashes(hashes) => write!(f, "{}", hashes),
353            Hashes::All => write!(f, "all"),
354        }
355    }
356}
357
358#[cfg_attr(feature = "builder", derive(typed_builder::TypedBuilder))]
359#[cfg_attr(
360    feature = "builder",
361    builder(field_defaults(default, setter(strip_option)))
362)]
363#[derive(Debug, Clone, PartialEq, Default, serde::Serialize)]
364#[skip_serializing_none]
365pub struct GetTorrentListArg {
366    /// Filter torrent list by state. Allowed state filters: `all`,
367    /// `downloading`, `seeding`, `completed`, `paused`, `active`, `inactive`,
368    /// `resumed`, `stalled`, `stalled_uploading`, `stalled_downloading`,
369    /// `errored`
370    pub filter: Option<TorrentFilter>,
371    /// Get torrents with the given category (empty string means "without category"; no "category" parameter means "any category" <- broken until [#11748](https://github.com/qbittorrent/qBittorrent/issues/11748) is resolved). Remember to URL-encode the category name. For example, `My category` becomes `My%20category`
372    pub category: Option<String>,
373    /// Get torrents with the given tag (empty string means "without tag"; no
374    /// "tag" parameter means "any tag". Remember to URL-encode the category
375    /// name. For example, `My tag` becomes `My%20tag`
376    pub tag: Option<String>,
377    /// Sort torrents by given key. They can be sorted using any field of the
378    /// response's JSON array (which are documented below) as the sort key.
379    pub sort: Option<String>,
380    /// Enable reverse sorting. Defaults to `false`
381    pub reverse: Option<bool>,
382    /// Limit the number of torrents returned
383    pub limit: Option<u64>,
384    /// Set offset (if less than 0, offset from end)
385    pub offset: Option<i64>,
386    /// Filter by hashes. Can contain multiple hashes separated by `\|`
387    pub hashes: Option<String>,
388}
389
390#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
391#[serde(untagged)]
392pub enum TorrentSource {
393    /// URLs
394    Urls { urls: Sep<Url, '\n'> },
395    /// Torrent files
396    TorrentFiles { torrents: Vec<TorrentFile> },
397}
398#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
399/// Torrent file
400pub struct TorrentFile {
401    pub filename: String,
402    pub data: Vec<u8>
403}
404impl Default for TorrentSource {
405    fn default() -> Self {
406        TorrentSource::Urls {
407            urls: Sep::from(vec![]),
408        }
409    }
410}
411fn is_torrent_files(source: &TorrentSource) -> bool {
412    matches!(source, TorrentSource::TorrentFiles { .. })
413}
414#[cfg_attr(feature = "builder", derive(typed_builder::TypedBuilder))]
415#[cfg_attr(
416    feature = "builder",
417    builder(field_defaults(default, setter(strip_option)))
418)]
419#[derive(Debug, Clone, PartialEq, serde::Serialize, Default)]
420#[skip_serializing_none]
421pub struct AddTorrentArg {
422    #[serde(flatten)]
423    #[cfg_attr(feature = "builder", builder(!default, setter(!strip_option)))]
424    #[serde(skip_serializing_if = "is_torrent_files")]
425    pub source: TorrentSource,
426    #[serde(skip_serializing_if = "Option::is_none")]
427    /// Download folder
428    pub savepath: Option<String>,
429    /// Cookie sent to download the .torrent file
430    #[serde(skip_serializing_if = "Option::is_none")]
431    pub cookie: Option<String>,
432    /// Category for the torrent
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub category: Option<String>,
435
436    /// Tags for the torrent, split by ','
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub tags: Option<String>,
439
440    /// Skip hash checking. Possible values are `true`, `false` (default)
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub skip_checking: Option<String>,
443
444    /// Add torrents in the paused state. Possible values are `true`, `false`
445    /// (default)
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub paused: Option<String>,
448
449    /// Create the root folder. Possible values are `true`, `false`, unset
450    /// (default)
451    #[serde(skip_serializing_if = "Option::is_none")]
452    pub root_folder: Option<String>,
453
454    /// Rename torrent
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub rename: Option<String>,
457
458    /// Set torrent upload speed limit. Unit in bytes/second
459    #[serde(rename = "upLimit")]
460    #[serde(skip_serializing_if = "Option::is_none")]
461    pub up_limit: Option<i64>,
462
463    /// Set torrent download speed limit. Unit in bytes/second
464    #[serde(rename = "dlLimit")]
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub download_limit: Option<i64>,
467
468    /// Set torrent share ratio limit
469    #[serde(rename = "ratioLimit")]
470    #[serde(skip_serializing_if = "Option::is_none")]
471    pub ratio_limit: Option<f64>,
472
473    /// Set torrent seeding time limit. Unit in minutes
474    #[serde(rename = "seedingTimeLimit")]
475    #[serde(skip_serializing_if = "Option::is_none")]
476    pub seeding_time_limit: Option<i64>,
477
478    /// Whether Automatic Torrent Management should be used
479    #[serde(rename = "autoTMM")]
480    #[serde(skip_serializing_if = "Option::is_none")]
481    pub auto_torrent_management: Option<bool>,
482
483    /// Enable sequential download. Possible values are `true`, `false`
484    /// (default)
485    #[serde(rename = "sequentialDownload")]
486    #[serde(skip_serializing_if = "Option::is_none")]
487    pub sequential_download: Option<String>,
488
489    /// Prioritize download first last piece. Possible values are `true`,
490    /// `false` (default)
491    #[serde(rename = "firstLastPiecePrio")]
492    #[serde(skip_serializing_if = "Option::is_none")]
493    pub first_last_piece_priority: Option<String>,
494}
495
496#[cfg_attr(feature = "builder", derive(typed_builder::TypedBuilder))]
497#[derive(Debug, Clone, PartialEq, serde::Serialize)]
498#[serde(rename_all = "camelCase")]
499pub struct SetTorrentSharedLimitArg {
500    #[cfg_attr(feature = "builder", builder(setter(into)))]
501    pub hashes: Hashes,
502    #[cfg_attr(feature = "builder", builder(default, setter(strip_option)))]
503    pub ratio_limit: Option<RatioLimit>,
504    #[cfg_attr(feature = "builder", builder(default, setter(strip_option)))]
505    pub seeding_time_limit: Option<SeedingTimeLimit>,
506    #[cfg_attr(feature = "builder", builder(default, setter(strip_option)))]
507    pub inactive_seeding_time_limit: Option<SeedingTimeLimit>,
508}
509
510#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
511pub enum RatioLimit {
512    Global,
513    NoLimit,
514    Limited(f64),
515}
516
517impl Serialize for RatioLimit {
518    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
519    where
520        S: serde::Serializer,
521    {
522        match self {
523            Self::Global => serializer.serialize_i64(-2),
524            Self::NoLimit => serializer.serialize_i64(-1),
525            Self::Limited(limit) => serializer.serialize_f64(*limit),
526        }
527    }
528}
529
530#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
531pub enum SeedingTimeLimit {
532    Global,
533    NoLimit,
534    /// Number of minutes
535    Limited(u64),
536}
537
538impl Serialize for SeedingTimeLimit {
539    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
540    where
541        S: serde::Serializer,
542    {
543        match self {
544            Self::Global => serializer.serialize_i64(-2),
545            Self::NoLimit => serializer.serialize_i64(-1),
546            Self::Limited(limit) => serializer.serialize_u64(*limit),
547        }
548    }
549}
550
551#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
552pub(crate) struct HashArg<'a> {
553    hash: &'a str,
554}
555
556impl<'a> HashArg<'a> {
557    pub(crate) fn new(hash: &'a str) -> Self {
558        Self { hash }
559    }
560}
561
562#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
563pub(crate) struct HashesArg {
564    hashes: Hashes,
565}
566
567impl HashesArg {
568    pub(crate) fn new(hashes: impl Into<Hashes> + Send + Sync) -> Self {
569        Self {
570            hashes: hashes.into(),
571        }
572    }
573}