Skip to main content

qbit_rs/model/
torrent.rs

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