Skip to main content

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