Skip to main content

qbit_rs/model/
mod.rs

1//! Model types used in the API.
2
3use std::{
4    fmt::{Display, Write},
5    path::PathBuf,
6    str::FromStr,
7};
8
9use serde::{Deserialize, Serialize};
10use serde_with::{DeserializeFromStr, SerializeDisplay};
11use tap::Pipe;
12
13mod_use::mod_use![app, log, sync, torrent, transfer, search];
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct ApiKey(pub String);
17
18/// Username and password used to authenticate with qBittorrent.
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct Credential {
21    username: String,
22    password: String,
23}
24
25impl Credential {
26    pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
27        Self {
28            username: username.into(),
29            password: password.into(),
30        }
31    }
32
33    /// Return a dummy credential when you passed in the cookie instead of
34    /// actual credential.
35    pub fn dummy() -> Self {
36        Self {
37            username: "".to_owned(),
38            password: "".to_owned(),
39        }
40    }
41
42    pub fn is_dummy(&self) -> bool {
43        self.username.is_empty() && self.password.is_empty()
44    }
45}
46
47#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
48#[serde(rename_all = "camelCase")]
49pub struct Category {
50    pub name: String,
51    pub save_path: PathBuf,
52}
53
54#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
55pub struct Tracker {
56    /// Tracker url
57    pub url: String,
58    /// Tracker status. See the table below for possible values
59    pub status: TrackerStatus,
60    /// Tracker priority tier. Lower tier trackers are tried before higher
61    /// tiers. Tier numbers are valid when `>= 0`, `< 0` is used as placeholder
62    /// when `tier` does not exist for special entries (such as DHT).
63    pub tier: i64,
64    /// Number of peers for current torrent, as reported by the tracker
65    pub num_peers: i64,
66    /// Number of seeds for current torrent, as reported by the tracker
67    pub num_seeds: i64,
68    /// Number of leeches for current torrent, as reported by the tracker
69    pub num_leeches: i64,
70    /// Number of completed downloads for current torrent, as reported by the
71    /// tracker
72    pub num_downloaded: i64,
73    /// Tracker message (there is no way of knowing what this message is - it's
74    /// up to tracker admins)
75    pub msg: String,
76    /// Next announce timestamp (added in qBittorrent 5.2.0, Web API v2.13.0)
77    #[serde(default)]
78    pub next_announce: i64,
79    /// Minimum announce interval in seconds (added in qBittorrent 5.2.0, Web API v2.13.0)
80    #[serde(default)]
81    pub min_announce: i64,
82}
83
84#[derive(
85    Debug,
86    Clone,
87    Copy,
88    PartialEq,
89    Eq,
90    PartialOrd,
91    Ord,
92    serde_repr::Serialize_repr,
93    serde_repr::Deserialize_repr,
94)]
95#[repr(i8)]
96pub enum TrackerStatus {
97    /// Tracker is disabled (used for DHT, PeX, and LSD)
98    Disabled     = 0,
99    /// Tracker has not been contacted yet
100    NotContacted = 1,
101    /// Tracker has been contacted and is working
102    Working      = 2,
103    /// Tracker is updating
104    Updating     = 3,
105    /// Tracker has been contacted, but it is not working (or doesn't send
106    /// proper replies)
107    NotWorking   = 4,
108    /// Tracker error (added in qBittorrent 5.2.0, Web API v2.13.0)
109    TrackerError = 5,
110    /// Tracker is unreachable (added in qBittorrent 5.2.0, Web API v2.13.0)
111    Unreachable  = 6,
112}
113
114/// Type that can be either an integer or a string.
115#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
116#[serde(untagged)]
117pub enum IntOrStr {
118    Int(i64),
119    Str(String),
120}
121
122impl Display for IntOrStr {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        match self {
125            IntOrStr::Int(i) => write!(f, "{i}"),
126            IntOrStr::Str(s) => write!(f, "{s}"),
127        }
128    }
129}
130
131/// A wrapper around `Vec<T>` that implements `FromStr` and `ToString` as
132/// `C`-separated strings where `C` is a char.
133#[derive(Debug, Clone, PartialEq, Eq, SerializeDisplay, DeserializeFromStr)]
134pub struct Sep<T, const C: char>(Vec<T>);
135
136impl<T: FromStr, const C: char> FromStr for Sep<T, C> {
137    type Err = T::Err;
138
139    fn from_str(s: &str) -> Result<Self, Self::Err> {
140        s.split(C)
141            .map(T::from_str)
142            .collect::<Result<Vec<_>, Self::Err>>()?
143            .pipe(Sep::from)
144            .pipe(Ok)
145    }
146}
147
148/// A wrapper around `str` that ensures the string is non-empty.
149pub struct NonEmptyStr<T>(T);
150
151impl<T: AsRef<str>> NonEmptyStr<T> {
152    pub fn as_str(&self) -> &str {
153        self.0.as_ref()
154    }
155
156    pub fn new(s: T) -> Option<Self> {
157        if s.as_ref().is_empty() {
158            None
159        } else {
160            Some(NonEmptyStr(s))
161        }
162    }
163}
164
165impl<T: Display, const C: char> Display for Sep<T, C> {
166    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167        match self.0.as_slice() {
168            [] => Ok(()),
169            [x] => x.fmt(f),
170            [x, xs @ ..] => {
171                x.fmt(f)?;
172                for x in xs {
173                    f.write_char(C)?;
174                    x.fmt(f)?;
175                }
176                Ok(())
177            }
178        }
179    }
180}
181
182impl<V: Into<Vec<T>>, T, const C: char> From<V> for Sep<T, C> {
183    fn from(inner: V) -> Self {
184        Sep(inner.into())
185    }
186}
187
188#[test]
189fn test_sep() {
190    let sep = Sep::<u8, '|'>::from(vec![1, 2, 3]);
191    assert_eq!(sep.to_string(), "1|2|3");
192
193    let sep = Sep::<u8, '\n'>::from(vec![1, 2, 3]);
194    assert_eq!(sep.to_string(), "1\n2\n3");
195
196    let sep = Sep::<u8, '|'>::from(vec![1]);
197    assert_eq!(sep.to_string(), "1");
198
199    let sep = Sep::<u8, '|'>::from(vec![]);
200    assert_eq!(sep.to_string(), "");
201}
202