1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
extern crate rss;
use core::str::FromStr;
use std::collections::HashMap;
use std::convert::TryFrom;
use chrono::Duration;
use rss::extension::Extension;
use rss::Item;
#[cfg(any(feature = "parse-names", feature = "require-parse-names"))]
use torrent_name_parser::Metadata;
use crate::Error;
#[derive(Clone, Debug, Default)]
pub struct Torrent {
pub name: String,
#[cfg(all(feature = "parse-names", not(feature = "require-parse-names")))]
pub metadata: Option<Metadata>,
#[cfg(feature = "require-parse-names")]
pub metadata: Metadata,
pub size: u64,
pub categories: Vec<u32>,
pub link: String,
pub seeders: Option<u16>,
pub leechers: Option<u16>,
pub minimum_ratio: Option<f32>,
pub minimum_seedtime: Option<Duration>
}
fn get_extension_value<'a>(ext: &'a HashMap<String, Vec<Extension>>, key: &str) -> Result<Option<&'a str>, Error> {
let ext = match ext.get("attr") {
Some(v) => v,
None => return Ok(None)
};
if(ext.len() == 0) {
return Err(Error::EmptyExtension(key.to_string()))
}
for extension in ext.iter() {
if let Some(name) = extension.attrs().get("name") {
if let Some(value) = extension.attrs().get("value") {
if(name == key) {
return Ok(Some(value));
}
}
}
}
Ok(None)
}
fn get_parsed_extension_value<T>(ext: &HashMap<String, Vec<Extension>>, key: &str) -> Result<Option<T>, Error>
where
T: FromStr,
Error: From<T::Err>
{
match get_extension_value(ext, key)? {
Some(v) => Ok(Some(v.parse::<T>()?)),
None => Ok(None)
}
}
impl TryFrom<Item> for Torrent {
type Error = Error;
fn try_from(item: Item) -> Result<Self, Self::Error> {
let mut this = Self{
name: item.title().ok_or(Error::MissingTitle)?.to_string(),
size: item.enclosure().ok_or(Error::MissingSize)?.length().parse()?,
categories: item.categories().iter().map(|category| category.name().parse::<u32>()).collect::<Result<Vec<_>, _>>()?,
link: item.link().ok_or(Error::MissingLink)?.to_string(),
..Default::default()
};
#[cfg(all(feature = "parse-names", not(feature = "require-parse-names")))]
{
this.metadata = Metadata::from(&this.name).ok();
}
#[cfg(feature = "require-parse-names")]
{
this.metadata = match Metadata::from(&this.name) {
Ok(v) => v,
Err(_) => return Err(Self::Error::parse_name_failure(&this.name))
};
}
if let Some(torznab) = item.extensions().get("torznab") {
this.seeders = match get_parsed_extension_value(&torznab, "seeders")? {
Some(seeders) => {
this.leechers = match get_parsed_extension_value::<u16>(&torznab, "peers")? {
Some(peers) => Some(peers - seeders),
None => None
};
Some(seeders)
},
None => None
};
this.minimum_ratio = get_parsed_extension_value(&torznab, "minimumrato")?;
this.minimum_seedtime = get_parsed_extension_value(&torznab, "minimumseedtime")?.map(|i| Duration::seconds(i));
}
Ok(this)
}
}