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    /// Number of distributed copies of the torrent's selected files.
249    /// Added in qBittorrent 5.2.0 (Web API v2.15.1).
250    pub availability: Option<f64>,
251    /// Torrent creator
252    pub created_by: Option<String>,
253    /// Torrent average download speed (bytes/second)
254    pub dl_speed_avg: Option<i64>,
255    /// Torrent download speed (bytes/second)
256    pub dl_speed: Option<i64>,
257    /// Torrent ETA (seconds)
258    pub eta: Option<i64>,
259    /// Last seen complete date (unix timestamp)
260    pub last_seen: Option<i64>,
261    /// Number of peers connected to
262    pub peers: Option<i64>,
263    /// Number of peers in the swarm
264    pub peers_total: Option<i64>,
265    /// Number of pieces owned
266    pub pieces_have: Option<i64>,
267    /// Number of pieces of the torrent
268    pub pieces_num: Option<i64>,
269    /// Number of seconds until the next announce
270    pub reannounce: Option<i64>,
271    /// Number of seeds connected to
272    pub seeds: Option<i64>,
273    /// Number of seeds in the swarm
274    pub seeds_total: Option<i64>,
275    /// Torrent total size (bytes)
276    pub total_size: Option<i64>,
277    /// Torrent average upload speed (bytes/second)
278    pub up_speed_avg: Option<i64>,
279    /// Torrent upload speed (bytes/second)
280    pub up_speed: Option<i64>,
281}
282
283#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
284pub struct WebSeed {
285    /// Web seed URL
286    pub url: Url,
287}
288
289#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
290pub struct TorrentContent {
291    /// File index
292    pub index: u64,
293    /// File name (including relative path),
294    pub name: String,
295    /// File size (bytes),
296    pub size: u64,
297    /// File progress (percentage/100),
298    pub progress: f64,
299    /// File priority. See possible values here below,
300    pub priority: Priority,
301    /// True if file is seeding/complete,
302    pub is_seed: Option<bool>,
303    /// The first number is the starting piece index and the second number is
304    /// the ending piece index (inclusive),
305    #[serde(default)]
306    pub piece_range: Vec<i64>,
307    /// Percentage of file pieces currently available (percentage/100),
308    #[serde(default)]
309    pub availability: f64,
310}
311
312#[derive(
313    Debug,
314    Clone,
315    Copy,
316    PartialEq,
317    Eq,
318    PartialOrd,
319    Ord,
320    serde_repr::Serialize_repr,
321    serde_repr::Deserialize_repr,
322)]
323#[repr(u8)]
324pub enum Priority {
325    /// Do not download
326    DoNotDownload = 0,
327    /// Normal priority
328    Normal        = 1,
329    /// Mixed
330    Mixed         = 4,
331    /// High priority
332    High          = 6,
333    /// Maximal priority
334    Maximal       = 7,
335}
336
337#[derive(
338    Debug,
339    Clone,
340    Copy,
341    PartialEq,
342    Eq,
343    PartialOrd,
344    Ord,
345    serde_repr::Serialize_repr,
346    serde_repr::Deserialize_repr,
347)]
348#[repr(u8)]
349pub enum PieceState {
350    /// Not downloaded yet
351    NotDownloaded = 0,
352    /// Now downloading
353    Downloading   = 1,
354    /// Already downloaded
355    Downloaded    = 2,
356}
357
358/// `|` separeated list of hash values or `all`
359#[derive(Debug, Clone, PartialEq, Eq, SerializeDisplay)]
360pub enum Hashes {
361    /// A list of torrent hashes separated by `|`
362    Hashes(Sep<String, '|'>),
363    /// All torrents
364    All,
365}
366
367impl<V: Into<Vec<String>>> From<V> for Hashes {
368    fn from(hashes: V) -> Self {
369        Hashes::Hashes(Sep::from(hashes))
370    }
371}
372
373impl Display for Hashes {
374    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
375        match self {
376            Hashes::Hashes(hashes) => write!(f, "{}", hashes),
377            Hashes::All => write!(f, "all"),
378        }
379    }
380}
381
382#[cfg_attr(feature = "builder", derive(typed_builder::TypedBuilder))]
383#[cfg_attr(
384    feature = "builder",
385    builder(field_defaults(default, setter(strip_option)))
386)]
387#[derive(Debug, Clone, PartialEq, Default, serde::Serialize)]
388#[skip_serializing_none]
389pub struct GetTorrentListArg {
390    /// Filter torrent list by state. Allowed state filters: `all`,
391    /// `downloading`, `seeding`, `completed`, `paused`, `active`, `inactive`,
392    /// `resumed`, `stalled`, `stalled_uploading`, `stalled_downloading`,
393    /// `errored`
394    pub filter: Option<TorrentFilter>,
395    /// 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`
396    pub category: Option<String>,
397    /// Get torrents with the given tag (empty string means "without tag"; no
398    /// "tag" parameter means "any tag". Remember to URL-encode the category
399    /// name. For example, `My tag` becomes `My%20tag`
400    pub tag: Option<String>,
401    /// Sort torrents by given key. They can be sorted using any field of the
402    /// response's JSON array (which are documented below) as the sort key.
403    pub sort: Option<String>,
404    /// Enable reverse sorting. Defaults to `false`
405    pub reverse: Option<bool>,
406    /// Limit the number of torrents returned
407    pub limit: Option<u64>,
408    /// Set offset (if less than 0, offset from end)
409    pub offset: Option<i64>,
410    /// Filter by hashes. Can contain multiple hashes separated by `\|`
411    pub hashes: Option<String>,
412}
413
414#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
415#[serde(untagged)]
416pub enum TorrentSource {
417    /// URLs
418    Urls { urls: Sep<Url, '\n'> },
419    /// Torrent files
420    TorrentFiles { torrents: Vec<TorrentFile> },
421}
422#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
423/// Torrent file
424pub struct TorrentFile {
425    pub filename: String,
426    pub data: Vec<u8>,
427}
428impl Default for TorrentSource {
429    fn default() -> Self {
430        TorrentSource::Urls {
431            urls: Sep::from(vec![]),
432        }
433    }
434}
435fn is_torrent_files(source: &TorrentSource) -> bool {
436    matches!(source, TorrentSource::TorrentFiles { .. })
437}
438#[cfg_attr(feature = "builder", derive(typed_builder::TypedBuilder))]
439#[cfg_attr(
440    feature = "builder",
441    builder(field_defaults(default, setter(strip_option)))
442)]
443#[derive(Debug, Clone, PartialEq, serde::Serialize, Default)]
444#[skip_serializing_none]
445pub struct AddTorrentArg {
446    #[serde(flatten)]
447    #[cfg_attr(feature = "builder", builder(!default, setter(!strip_option)))]
448    #[serde(skip_serializing_if = "is_torrent_files")]
449    pub source: TorrentSource,
450    #[serde(skip_serializing_if = "Option::is_none")]
451    /// Download folder
452    pub savepath: Option<String>,
453    /// Cookie sent to download the .torrent file
454    #[serde(skip_serializing_if = "Option::is_none")]
455    pub cookie: Option<String>,
456    /// Download torrent using a search plugin (added in qBittorrent 5.2.0, Web API v2.13.1).
457    /// Specify the search plugin name to use for downloading.
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub downloader: Option<String>,
460    /// Category for the torrent
461    #[serde(skip_serializing_if = "Option::is_none")]
462    pub category: Option<String>,
463
464    /// Tags for the torrent, split by ','
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub tags: Option<String>,
467
468    /// Skip hash checking. Possible values are `true`, `false` (default)
469    #[serde(skip_serializing_if = "Option::is_none")]
470    pub skip_checking: Option<String>,
471
472    /// Add torrents in the paused state. Possible values are `true`, `false`
473    /// (default).
474    ///
475    /// This is the field name used by qBittorrent before 5.0; version 5.0+
476    /// renamed it to [`stopped`](Self::stopped). You only need to set one of
477    /// the two — [`add_torrent`](crate::Qbit::add_torrent) copies whichever is
478    /// set into the other so the request is honored regardless of the server
479    /// version. If both are set, they are sent as-is.
480    #[serde(skip_serializing_if = "Option::is_none")]
481    pub paused: Option<String>,
482
483    /// Add torrents in the stopped state. Possible values are `true`, `false`
484    /// (default).
485    ///
486    /// This is the field name used by qBittorrent 5.0+; it is the successor of
487    /// [`paused`](Self::paused). You only need to set one of the two —
488    /// [`add_torrent`](crate::Qbit::add_torrent) copies whichever is set into
489    /// the other so the request is honored regardless of the server version.
490    /// If both are set, they are sent as-is.
491    #[serde(skip_serializing_if = "Option::is_none")]
492    pub stopped: Option<String>,
493
494    /// Create the root folder. Possible values are `true`, `false`, unset
495    /// (default)
496    #[serde(skip_serializing_if = "Option::is_none")]
497    pub root_folder: Option<String>,
498
499    /// Content layout of the torrent, controlling how the downloaded files are
500    /// placed on disk. Unset uses the server's default. Supersedes
501    /// [`root_folder`](Self::root_folder) on qBittorrent 4.3.2+.
502    #[serde(rename = "contentLayout")]
503    #[serde(skip_serializing_if = "Option::is_none")]
504    pub content_layout: Option<ContentLayout>,
505
506    /// Rename torrent
507    #[serde(skip_serializing_if = "Option::is_none")]
508    pub rename: Option<String>,
509
510    /// Set torrent upload speed limit. Unit in bytes/second
511    #[serde(rename = "upLimit")]
512    #[serde(skip_serializing_if = "Option::is_none")]
513    pub up_limit: Option<i64>,
514
515    /// Set torrent download speed limit. Unit in bytes/second
516    #[serde(rename = "dlLimit")]
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub download_limit: Option<i64>,
519
520    /// Set torrent share ratio limit
521    #[serde(rename = "ratioLimit")]
522    #[serde(skip_serializing_if = "Option::is_none")]
523    pub ratio_limit: Option<f64>,
524
525    /// Set torrent seeding time limit. Unit in minutes
526    #[serde(rename = "seedingTimeLimit")]
527    #[serde(skip_serializing_if = "Option::is_none")]
528    pub seeding_time_limit: Option<i64>,
529
530    /// Whether Automatic Torrent Management should be used
531    #[serde(rename = "autoTMM")]
532    #[serde(skip_serializing_if = "Option::is_none")]
533    pub auto_torrent_management: Option<bool>,
534
535    /// Enable sequential download. Possible values are `true`, `false`
536    /// (default)
537    #[serde(rename = "sequentialDownload")]
538    #[serde(skip_serializing_if = "Option::is_none")]
539    pub sequential_download: Option<String>,
540
541    /// Prioritize download first last piece. Possible values are `true`,
542    /// `false` (default)
543    #[serde(rename = "firstLastPiecePrio")]
544    #[serde(skip_serializing_if = "Option::is_none")]
545    pub first_last_piece_priority: Option<String>,
546}
547
548/// How the files of a torrent are laid out on disk when added.
549#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
550pub enum ContentLayout {
551    /// Use the layout as defined by the torrent itself.
552    Original,
553    /// Always create a subfolder containing the torrent's files.
554    Subfolder,
555    /// Never create a subfolder, placing files directly in the save path.
556    NoSubfolder,
557}
558
559#[cfg(test)]
560mod content_layout_tests {
561    use super::ContentLayout;
562
563    #[test]
564    fn serializes_to_api_names() {
565        for (variant, expected) in [
566            (ContentLayout::Original, "Original"),
567            (ContentLayout::Subfolder, "Subfolder"),
568            (ContentLayout::NoSubfolder, "NoSubfolder"),
569        ] {
570            assert_eq!(serde_json::to_value(variant).unwrap(), expected);
571        }
572    }
573}
574
575#[cfg(test)]
576mod torrent_source_tests {
577    use super::{AddTorrentArg, TorrentFile, TorrentSource};
578
579    /// Regression test for <https://github.com/George-Miao/qbit/issues/11>:
580    /// adding a torrent from disk used to fail with `unsupported value`
581    /// because the binary payload was fed to `serde_urlencoded`. The raw file
582    /// bytes are now sent as a separate multipart part, so the arg itself must
583    /// serialize without the `source`/`torrents` field.
584    #[test]
585    fn torrent_files_source_is_skipped_on_serialization() {
586        let arg = AddTorrentArg {
587            source: TorrentSource::TorrentFiles {
588                torrents: vec![TorrentFile {
589                    filename: "test.torrent".to_owned(),
590                    // Non-UTF-8 bytes that `serde_urlencoded` cannot encode.
591                    data: vec![0x64, 0x38, 0x00, 0xff, 0xfe],
592                }],
593            },
594            ..Default::default()
595        };
596
597        let value = serde_json::to_value(&arg).unwrap();
598        let obj = value.as_object().expect("should serialize to an object");
599        assert!(
600            !obj.contains_key("torrents") && !obj.contains_key("source"),
601            "binary torrent payload must not be serialized into the form: {obj:?}"
602        );
603
604        // The trimmed-down arg is now safe to urlencode.
605        serde_urlencoded::to_string(&arg).expect("arg without binary source must urlencode");
606    }
607}
608
609#[cfg_attr(feature = "builder", derive(typed_builder::TypedBuilder))]
610#[derive(Debug, Clone, PartialEq, serde::Serialize)]
611#[serde(rename_all = "camelCase")]
612pub struct SetTorrentSharedLimitArg {
613    #[cfg_attr(feature = "builder", builder(setter(into)))]
614    pub hashes: Hashes,
615    #[cfg_attr(feature = "builder", builder(default, setter(strip_option)))]
616    pub ratio_limit: Option<RatioLimit>,
617    #[cfg_attr(feature = "builder", builder(default, setter(strip_option)))]
618    pub seeding_time_limit: Option<SeedingTimeLimit>,
619    #[cfg_attr(feature = "builder", builder(default, setter(strip_option)))]
620    pub inactive_seeding_time_limit: Option<SeedingTimeLimit>,
621}
622
623#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
624pub enum RatioLimit {
625    Global,
626    NoLimit,
627    Limited(f64),
628}
629
630impl Serialize for RatioLimit {
631    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
632    where
633        S: serde::Serializer,
634    {
635        match self {
636            Self::Global => serializer.serialize_i64(-2),
637            Self::NoLimit => serializer.serialize_i64(-1),
638            Self::Limited(limit) => serializer.serialize_f64(*limit),
639        }
640    }
641}
642
643#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
644pub enum SeedingTimeLimit {
645    Global,
646    NoLimit,
647    /// Number of minutes
648    Limited(u64),
649}
650
651impl Serialize for SeedingTimeLimit {
652    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
653    where
654        S: serde::Serializer,
655    {
656        match self {
657            Self::Global => serializer.serialize_i64(-2),
658            Self::NoLimit => serializer.serialize_i64(-1),
659            Self::Limited(limit) => serializer.serialize_u64(*limit),
660        }
661    }
662}
663
664#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
665pub(crate) struct HashArg<'a> {
666    hash: &'a str,
667}
668
669impl<'a> HashArg<'a> {
670    pub(crate) fn new(hash: &'a str) -> Self {
671        Self { hash }
672    }
673}
674
675#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
676pub(crate) struct HashesArg {
677    hashes: Hashes,
678}
679
680impl HashesArg {
681    pub(crate) fn new(hashes: impl Into<Hashes> + Send + Sync) -> Self {
682        Self {
683            hashes: hashes.into(),
684        }
685    }
686}