use chrono::{DateTime, NaiveDate, TimeZone, Utc};
use error::ListError;
use list::{self, Status};
use minidom::Element;
use request::ListType;
use SeriesInfo;
use super::{ChangeTracker, EntryValues, ListEntry, UserInfo};
#[derive(Debug, Clone)]
pub struct AnimeInfo {
pub id: u32,
pub title: String,
pub english_title: Option<String>,
pub synonyms: Vec<String>,
pub episodes: u32,
pub series_type: AnimeType,
pub airing_status: AiringStatus,
pub start_date: Option<NaiveDate>,
pub end_date: Option<NaiveDate>,
pub synopsis: Option<String>,
pub image_url: String,
}
impl SeriesInfo for AnimeInfo {
#[doc(hidden)]
fn parse_search_result(xml: &Element) -> Result<AnimeInfo, ListError> {
let entry = AnimeInfo {
id: list::parse_xml_child(xml, "id")?,
title: list::parse_xml_child(xml, "title")?,
english_title: match list::parse_xml_child::<String>(xml, "english") {
Ok(ref title) if title.is_empty() => None,
Ok(title) => Some(title),
Err(e) => return Err(e),
},
synonyms: {
list::split_by_delim(&list::parse_xml_child::<String>(xml, "synonyms")?, "; ")
},
episodes: list::parse_xml_child(xml, "episodes")?,
series_type: {
let s_type = list::parse_xml_child(xml, "type")?;
AnimeType::from_str(&s_type).ok_or_else(|| ListError::UnknownSeriesType(s_type))?
},
airing_status: {
let status = list::parse_xml_child(xml, "status")?;
AiringStatus::from_str(&status).ok_or_else(|| ListError::UnknownStatus(status))?
},
start_date: list::parse_str_date(&list::parse_xml_child::<String>(xml, "start_date")?),
end_date: list::parse_str_date(&list::parse_xml_child::<String>(xml, "end_date")?),
synopsis: Some(list::parse_xml_child(xml, "synopsis")?),
image_url: list::parse_xml_child(xml, "image")?,
};
Ok(entry)
}
}
impl PartialEq for AnimeInfo {
#[inline]
fn eq(&self, other: &AnimeInfo) -> bool {
self.id == other.id
}
}
gen_list_field_enum!(AnimeType,
["A unknown series type (usually because it hasn't aired yet)."]
Unknown = [0, ""],
["A series that has aired on TV."]
TV = [1, "tv"],
["A series that has never aired on TV."]
OVA = [2, "ova"],
["A series depicted in the form of a movie."]
Movie = [3, "movie"],
["An extra set of episodes from a series that are usually self-contained."]
Special = [4, "special"],
["A series that has only been presented on the internet."]
ONA = [5, "ona"],
["A music video."]
Music = [6, "music"],
);
gen_list_field_enum!(AiringStatus,
["A series that is currently airing."]
Airing = [1, "currently airing"],
["A series that has finished airing."]
FinishedAiring = [2, "finished airing"],
["A series that hasn't aired yet."]
NotYetAired = [3, "not yet aired"],
);
#[derive(Debug, Clone)]
pub struct AnimeEntry {
pub series_info: AnimeInfo,
pub last_updated_time: DateTime<Utc>,
pub values: AnimeValues,
}
impl AnimeEntry {
#[inline]
pub fn new(info: AnimeInfo) -> AnimeEntry {
AnimeEntry {
series_info: info,
last_updated_time: Utc::now(),
values: AnimeValues::new(),
}
}
}
impl ListEntry for AnimeEntry {
type Info = AnimeInfo;
type Values = AnimeValues;
type UserInfo = AnimeUserInfo;
#[doc(hidden)]
fn from_xml(xml: &Element) -> Result<AnimeEntry, ListError> {
let info = AnimeInfo {
id: list::parse_xml_child(xml, "series_animedb_id")?,
title: list::parse_xml_child(xml, "series_title")?,
english_title: None,
synonyms: {
list::split_by_delim(
&list::parse_xml_child::<String>(xml, "series_synonyms")?,
"; ",
)
},
episodes: list::parse_xml_child(xml, "series_episodes")?,
series_type: {
let s_type = list::parse_xml_child(xml, "series_type")?;
AnimeType::from_i32(s_type)
.ok_or_else(|| ListError::UnknownSeriesType(s_type.to_string()))?
},
airing_status: {
let status = list::parse_xml_child(xml, "series_status")?;
AiringStatus::from_i32(status)
.ok_or_else(|| ListError::UnknownStatus(status.to_string()))?
},
start_date: {
list::parse_str_date(&list::parse_xml_child::<String>(xml, "series_start")?)
},
end_date: list::parse_str_date(&list::parse_xml_child::<String>(xml, "series_end")?),
synopsis: None,
image_url: list::parse_xml_child(xml, "series_image")?,
};
let entry = AnimeEntry {
series_info: info,
last_updated_time: Utc.timestamp(list::parse_xml_child(xml, "my_last_updated")?, 0),
values: AnimeValues::from_xml(xml)?,
};
Ok(entry)
}
#[doc(hidden)]
#[inline]
fn values_mut(&mut self) -> &mut AnimeValues {
&mut self.values
}
#[doc(hidden)]
#[inline]
fn set_last_updated_time(&mut self) {
self.last_updated_time = Utc::now();
}
#[doc(hidden)]
#[inline]
fn id(&self) -> u32 {
self.series_info.id
}
#[doc(hidden)]
#[inline]
fn list_type() -> ListType {
ListType::Anime
}
}
impl PartialEq for AnimeEntry {
#[inline]
fn eq(&self, other: &AnimeEntry) -> bool {
self.series_info == other.series_info
}
}
#[derive(Debug, Default, Clone)]
pub struct AnimeValues {
watched_episodes: ChangeTracker<u32>,
start_date: ChangeTracker<Option<NaiveDate>>,
finish_date: ChangeTracker<Option<NaiveDate>>,
status: ChangeTracker<Status>,
score: ChangeTracker<u8>,
rewatching: ChangeTracker<bool>,
tags: ChangeTracker<Vec<String>>,
}
impl AnimeValues {
#[inline]
pub fn new() -> AnimeValues {
AnimeValues::default()
}
fn from_xml(xml: &Element) -> Result<AnimeValues, ListError> {
let values = AnimeValues {
watched_episodes: list::parse_xml_child::<u32>(xml, "my_watched_episodes")?.into(),
start_date: {
list::parse_str_date(&list::parse_xml_child::<String>(xml, "my_start_date")?).into()
},
finish_date: {
list::parse_str_date(&list::parse_xml_child::<String>(xml, "my_finish_date")?)
.into()
},
status: {
let status_num = list::parse_xml_child(xml, "my_status")?;
Status::from_i32(status_num)
.ok_or_else(|| ListError::UnknownStatus(status_num.to_string()))?
.into()
},
score: list::parse_xml_child::<u8>(xml, "my_score")?.into(),
rewatching: {
list::parse_xml_child::<u8>(xml, "my_rewatching")
.map(|v| v == 1)
.unwrap_or(false)
.into()
},
tags: {
list::split_by_delim(&list::parse_xml_child::<String>(xml, "my_tags")?, ",").into()
},
};
Ok(values)
}
#[inline]
pub fn tags(&self) -> &Vec<String> {
&self.tags.value
}
#[inline]
pub fn tags_mut(&mut self) -> &mut Vec<String> {
self.tags.changed = true;
&mut self.tags.value
}
}
impl_tracker_getset!(AnimeValues,
[watched_episodes, set_watched_episodes, "number of watched episodes"]: u32,
[start_date, set_start_date, "date the user started watching the series"]: Option<NaiveDate>,
[finish_date, set_finish_date, "date the user finished watching the series"]: Option<NaiveDate>,
[status, set_status, "current watch status of the series"]: Status,
[score, set_score, "user's rating of the series"]: u8,
[rewatching, set_rewatching, "current rewatch status of the series"]: bool,
);
impl_entryvalues!(AnimeValues,
watched_episodes(num): "episode" => num.to_string(),
status(status): "status" => (*status as i32).to_string(),
start_date(date): "date_start" => list::date_to_str(*date),
finish_date(date): "date_finish" => list::date_to_str(*date),
score(score): "score" => score.to_string(),
rewatching(v): "enable_rewatching" => (*v as u8).to_string(),
tags(t): "tags" => list::concat_by_delim(t, ','),
);
#[derive(Debug, Clone)]
pub struct AnimeUserInfo {
pub user_id: u32,
pub watching: u32,
pub completed: u32,
pub on_hold: u32,
pub dropped: u32,
pub plan_to_watch: u32,
pub days_spent_watching: f32,
}
impl UserInfo for AnimeUserInfo {
#[doc(hidden)]
fn from_xml(xml: &Element) -> Result<AnimeUserInfo, ListError> {
let info = AnimeUserInfo {
user_id: list::parse_xml_child(xml, "user_id")?,
watching: list::parse_xml_child(xml, "user_watching")?,
completed: list::parse_xml_child(xml, "user_completed")?,
on_hold: list::parse_xml_child(xml, "user_onhold")?,
dropped: list::parse_xml_child(xml, "user_dropped")?,
plan_to_watch: list::parse_xml_child(xml, "user_plantowatch")?,
days_spent_watching: list::parse_xml_child(xml, "user_days_spent_watching")?,
};
Ok(info)
}
}