#[serde_with::serde_as]
#[derive(Clone,Debug,Default,serde::Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all="kebab-case")]
pub struct Match {
pub any: Option<Vec<Match>>,
pub categories: Option<std::collections::HashSet<String>>,
pub is_private: Option<bool>,
#[serde_as(as="Option<crate::Cmp<crate::SerdeDuration>>")] pub last_activity: Option<crate::Cmp<std::time::Duration>>,
pub leech_count: Option<crate::Cmp<u64>>,
#[serde_as(as="Option<serde_with::FromInto<crate::SerdeRegexSet>>")] pub name: Option<regex::RegexSet>,
#[serde(default)] pub none: Vec<Match>,
pub ratio: Option<crate::Cmp<f32>>,
#[serde_as(as="Option<crate::Cmp<crate::SerdeDuration>>")] pub seed_time: Option<crate::Cmp<std::time::Duration>>,
pub seed_count: Option<crate::Cmp<u64>>,
pub state: Option<std::collections::HashSet<crate::TorrentState>>,
#[serde_as(as="Option<serde_with::FromInto<crate::SerdeRegexSet>>")] pub tracker_msg: Option<regex::RegexSet>,
pub tracker_status: Option<std::collections::HashSet<crate::TrackerStatus>>,
#[serde_as(as="Option<serde_with::FromInto<crate::SerdeRegexSet>>")] pub tracker_url: Option<regex::RegexSet>,
}
impl Match {
pub fn match_fast(
&self,
now: std::time::SystemTime,
t: &crate::Torrent,
) -> Option<bool> {
let mut need_slow = false;
if self.is_private.is_some_and(|p| Some(p) != t.private) {
return Some(false)
}
if let Some(last_activity) = &self.last_activity {
let since = now.duration_since(t.last_activity)
.unwrap_or_default();
if !last_activity.matches(&since) {
return Some(false)
}
}
if self.ratio.as_ref().is_some_and(|r| !r.matches(&t.ratio)) {
return Some(false)
}
if self.seed_time.as_ref().is_some_and(|s| !s.matches(&t.seeding_time)) {
return Some(false)
}
if self.leech_count.as_ref().is_some_and(|l| !l.matches(&t.leechers)) {
return Some(false)
}
if self.seed_count.as_ref().is_some_and(|s| !s.matches(&t.seeders)) {
return Some(false)
}
if self.state.as_ref().is_some_and(|s| !s.contains(&t.state)) {
return Some(false)
}
if self.categories.as_ref().is_some_and(|c| !c.contains(&t.category)) {
return Some(false)
}
if self.name.as_ref().is_some_and(|n| !n.is_match(&t.name)) {
return Some(false)
}
for pred in &self.none {
match pred.match_fast(now, t) {
Some(true) => return Some(false),
Some(false) => continue,
None => need_slow = true,
}
}
if let Some(any) = &self.any {
let mut r = Some(false);
for a in any {
match a.match_fast(now, t) {
Some(true) => {
r = Some(true);
break
},
Some(false) => continue,
None => r = None,
}
}
match r {
Some(true) => {}
Some(false) => return Some(false),
None => need_slow = true,
}
}
need_slow |=
self.tracker_msg.is_some()
|| self.tracker_status.is_some()
|| self.tracker_url.is_some();
if need_slow {
None
} else {
Some(true)
}
}
pub fn match_slow(
&self,
trackers: &[crate::Tracker],
) -> bool {
for pred in &self.none {
if pred.match_slow(trackers) {
return false
}
}
if let Some(any) = &self.any {
let mut r = false;
for a in any {
if a.match_slow(trackers) {
r = true;
break
}
}
if !r {
return false
}
}
if let Some(tracker_msg) = &self.tracker_msg {
let mut r = false;
for tr in trackers {
if tracker_msg.is_match(&tr.msg) {
r = true;
break
}
}
if !r {
return false
}
}
if self.tracker_status.as_ref()
.is_some_and(|s|
trackers.iter().all(|tr| !s.contains(&tr.status)))
{
return false
}
if let Some(tracker_url) = &self.tracker_url {
let mut r = false;
for tr in trackers {
if tracker_url.is_match(&tr.url) {
r = true;
break
}
}
if !r {
return false
}
}
true
}
}