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