use std::{
fmt::{Display, Write},
path::PathBuf,
str::FromStr,
};
use serde::{Deserialize, Serialize};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use tap::Pipe;
mod_use::mod_use![app, log, sync, torrent, transfer, search];
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Credential {
username: String,
password: String,
}
impl Credential {
pub fn new(username: impl Into<String>, password: impl Into<String>) -> Self {
Self {
username: username.into(),
password: password.into(),
}
}
pub fn dummy() -> Self {
Self {
username: "".to_owned(),
password: "".to_owned(),
}
}
pub fn is_dummy(&self) -> bool {
self.username.is_empty() && self.password.is_empty()
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Category {
pub name: String,
pub save_path: PathBuf,
}
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
pub struct Tracker {
pub url: String,
pub status: TrackerStatus,
pub tier: i64,
pub num_peers: i64,
pub num_seeds: i64,
pub num_leeches: i64,
pub num_downloaded: i64,
pub msg: String,
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
serde_repr::Serialize_repr,
serde_repr::Deserialize_repr,
)]
#[repr(i8)]
pub enum TrackerStatus {
Disabled = 0,
NotContacted = 1,
Working = 2,
Updating = 3,
NotWorking = 4,
}
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum IntOrStr {
Int(i64),
Str(String),
}
impl Display for IntOrStr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IntOrStr::Int(i) => write!(f, "{i}"),
IntOrStr::Str(s) => write!(f, "{s}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, SerializeDisplay, DeserializeFromStr)]
pub struct Sep<T, const C: char>(Vec<T>);
impl<T: FromStr, const C: char> FromStr for Sep<T, C> {
type Err = T::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.split(C)
.map(T::from_str)
.collect::<Result<Vec<_>, Self::Err>>()?
.pipe(Sep::from)
.pipe(Ok)
}
}
pub struct NonEmptyStr<T>(T);
impl<T: AsRef<str>> NonEmptyStr<T> {
pub fn as_str(&self) -> &str {
self.0.as_ref()
}
pub fn new(s: T) -> Option<Self> {
if s.as_ref().is_empty() {
None
} else {
Some(NonEmptyStr(s))
}
}
}
impl<T: Display, const C: char> Display for Sep<T, C> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0.as_slice() {
[] => Ok(()),
[x] => x.fmt(f),
[x, xs @ ..] => {
x.fmt(f)?;
for x in xs {
f.write_char(C)?;
x.fmt(f)?;
}
Ok(())
}
}
}
}
impl<V: Into<Vec<T>>, T, const C: char> From<V> for Sep<T, C> {
fn from(inner: V) -> Self {
Sep(inner.into())
}
}
#[test]
fn test_sep() {
let sep = Sep::<u8, '|'>::from(vec![1, 2, 3]);
assert_eq!(sep.to_string(), "1|2|3");
let sep = Sep::<u8, '\n'>::from(vec![1, 2, 3]);
assert_eq!(sep.to_string(), "1\n2\n3");
let sep = Sep::<u8, '|'>::from(vec![1]);
assert_eq!(sep.to_string(), "1");
let sep = Sep::<u8, '|'>::from(vec![]);
assert_eq!(sep.to_string(), "");
}