nyauser_types/
pull_entry.rs

1use std::{
2    cmp::Ordering,
3    collections::HashMap,
4    fmt,
5    path::{Path, PathBuf},
6    str::FromStr,
7};
8
9use chrono::{DateTime, FixedOffset};
10use serde::{Deserialize, Serialize};
11
12use anyhow::Result;
13
14#[derive(Clone, Debug, Serialize, Deserialize)]
15pub struct PullEntryFilter {
16    pub profile: Option<String>,
17    pub title_contains: Option<String>,
18    pub title_is: Option<String>,
19    pub season_is: Option<u32>,
20    pub episode_is: Option<Episode>,
21    pub state: Option<PullState>,
22}
23
24#[derive(Clone, Debug, Serialize, Deserialize)]
25pub struct SearchResult {
26    pub title: String,
27    pub torrent_link: String,
28    pub view_link: String,
29    pub date: DateTime<FixedOffset>,
30    pub seeders: u64,
31    pub leechers: u64,
32    pub downloads: u64,
33    pub size: u64,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
37#[serde(untagged)]
38pub enum Episode {
39    Standard(u32),
40    Special(String),
41}
42
43impl PartialOrd for Episode {
44    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
45        match (self, other) {
46            (Episode::Standard(e1), Episode::Standard(e2)) => e1.partial_cmp(e2),
47            (Episode::Standard(_), Episode::Special(_)) => Some(Ordering::Less),
48            (Episode::Special(_), Episode::Standard(_)) => Some(Ordering::Greater),
49            (Episode::Special(e1), Episode::Special(e2)) => e1.partial_cmp(e2),
50        }
51    }
52}
53
54impl Ord for Episode {
55    fn cmp(&self, other: &Self) -> Ordering {
56        self.partial_cmp(other).unwrap()
57    }
58}
59
60impl FromStr for Episode {
61    type Err = ();
62
63    fn from_str(s: &str) -> Result<Self, Self::Err> {
64        if let Some(s) = s.parse().ok() {
65            Ok(Episode::Standard(s))
66        } else {
67            Ok(Episode::Special(s.to_string()))
68        }
69    }
70}
71
72impl Default for Episode {
73    fn default() -> Self {
74        Episode::Standard(0)
75    }
76}
77
78impl fmt::Display for Episode {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        match self {
81            Episode::Standard(x) => write!(f, "{}", x),
82            Episode::Special(x) => write!(f, "{}", x),
83        }
84    }
85}
86
87#[derive(Default, Debug, Clone, Serialize, Deserialize)]
88pub struct StandardEpisode {
89    pub title: String,
90    pub season: u32,
91    pub episode: Episode,
92    pub checksum: u32,
93    pub ext: HashMap<String, String>,
94}
95
96#[derive(Serialize, Deserialize)]
97pub struct PullEntryNamed {
98    pub id: String,
99    #[serde(flatten)]
100    pub pull_entry: PullEntry,
101}
102
103#[derive(Debug, Serialize, Deserialize)]
104pub struct PullEntry {
105    pub result: ParsedSearchResult,
106    pub torrent_id: Option<i64>,
107    pub torrent_hash: String,
108    pub state: PullState,
109    #[serde(default)]
110    pub files: Vec<String>,
111}
112
113impl PullEntry {
114    pub fn key(&self) -> String {
115        self.result.key()
116    }
117}
118
119#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
120pub enum PullState {
121    Downloading,
122    Finished,
123}
124
125impl fmt::Display for PullState {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        match self {
128            PullState::Downloading => write!(f, "downloading"),
129            PullState::Finished => write!(f, "finished"),
130        }
131    }
132}
133
134impl FromStr for PullState {
135    type Err = ();
136
137    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
138        match s {
139            "downloading" => Ok(PullState::Downloading),
140            "finished" => Ok(PullState::Finished),
141            _ => Err(()),
142        }
143    }
144}
145
146#[derive(Clone, Debug, Serialize, Deserialize)]
147pub struct ParsedSearchResult {
148    pub result: SearchResult,
149    pub parsed: StandardEpisode,
150    pub profile: String,
151    pub relocate: Option<String>,
152    pub relocate_season: bool,
153}
154
155impl ParsedSearchResult {
156    pub fn key(&self) -> String {
157        format!(
158            "{}_S{:02}E{:02}",
159            self.parsed.title, self.parsed.season, self.parsed.episode
160        )
161    }
162
163    pub fn relocate_dir(&self) -> Option<PathBuf> {
164        let mut relocate = Path::new(self.relocate.as_ref()?).to_owned();
165        if self.relocate_season {
166            relocate = relocate.join(format!("Season {}", self.parsed.season));
167        }
168        Some(relocate)
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn test_episode_order() {
178        assert!(Episode::Standard(5) < Episode::Standard(10));
179        assert!(Episode::Standard(15) < Episode::Special("test".to_string()));
180        assert!(Episode::Special("2".to_string()) < Episode::Special("20".to_string()));
181    }
182}