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>, // In theory, u32 is good for our use case
	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)
	}
}