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)]
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 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 pub url: String,
55 pub status: TrackerStatus,
57 pub tier: i64,
61 pub num_peers: i64,
63 pub num_seeds: i64,
65 pub num_leeches: i64,
67 pub num_downloaded: i64,
70 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 Disabled = 0,
90 NotContacted = 1,
92 Working = 2,
94 Updating = 3,
96 NotWorking = 4,
99}
100
101#[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#[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
135pub 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}