1use 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#[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 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 pub url: String,
58 pub status: TrackerStatus,
60 pub tier: i64,
64 pub num_peers: i64,
66 pub num_seeds: i64,
68 pub num_leeches: i64,
70 pub num_downloaded: i64,
73 pub msg: String,
76 #[serde(default)]
78 pub next_announce: i64,
79 #[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 Disabled = 0,
99 NotContacted = 1,
101 Working = 2,
103 Updating = 3,
105 NotWorking = 4,
108 TrackerError = 5,
110 Unreachable = 6,
112}
113
114#[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#[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
148pub 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