use chrono::NaiveDate;
use error::{MALError, ListError, RequestError};
use MAL;
use minidom::Element;
use request::{ListType, Request};
use reqwest::StatusCode;
use std::fmt::{self, Debug, Display};
use std::marker::PhantomData;
use std::str::FromStr;
macro_rules! impl_tracker_getset {
($name:ident, $([$field:ident, $setter:ident, $verb:expr]: $field_type:ty,)+) => {
impl $name {
$(
#[doc = "Returns the "]
#[doc = $verb]
#[doc = "."]
#[inline]
pub fn $field(&self) -> $field_type {
self.$field.value
}
#[doc = "Sets the "]
#[doc = $verb]
#[doc = "."]
#[inline]
pub fn $setter(&mut self, $field: $field_type) -> &mut $name {
self.$field.set($field);
self
}
)+
}
};
}
macro_rules! gen_list_field_enum {
($name:ident, $([$field_doc:expr] $field:ident = [$field_index:expr, $field_str:expr],)+) => {
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum $name {
$(
#[doc = $field_doc]
$field = $field_index,
)+
}
impl $name {
#[inline]
pub fn from_i32(value: i32) -> Option<$name> {
match value {
$($field_index => Some($name::$field),)+
_ => None,
}
}
fn from_str<S: AsRef<str>>(input: S) -> Option<$name> {
let lowered = input.as_ref().to_ascii_lowercase();
match lowered.as_str() {
$($field_str => Some($name::$field),)+
_ => None,
}
}
}
};
}
macro_rules! impl_entryvalues {
($struct:ident, $($field:ident($val_name:ident): $xml_name:expr => $xml_val:expr,)+) => {
impl EntryValues for $struct {
#[doc(hidden)]
fn add_changed_values(&self, xml_elem: &mut Element) {
$(if self.$field.changed {
let $val_name = &self.$field.value;
let mut elem = Element::bare($xml_name);
elem.append_text_node($xml_val);
xml_elem.append_child(elem);
})+
}
#[doc(hidden)]
fn reset_changed_fields(&mut self) {
$(self.$field.changed = false;)+
}
}
};
}
#[cfg(feature = "anime")]
pub mod anime;
#[cfg(feature = "manga")]
pub mod manga;
#[derive(Debug, Clone, Copy)]
pub struct List<'a, E: ListEntry> {
pub mal: &'a MAL<'a>,
_list_entry: PhantomData<E>,
}
impl<'a, E: ListEntry> List<'a, E> {
#[inline]
pub fn new(mal: &'a MAL) -> List<'a, E> {
List {
mal,
_list_entry: PhantomData,
}
}
pub fn search_for<S>(&self, name: S) -> Result<Vec<E::Info>, MALError>
where
S: AsRef<str>,
{
let resp = {
let result = Request::Search(name.as_ref(), E::list_type()).send(self.mal);
match result {
Ok(resp) => resp,
Err(RequestError::BadResponseCode(StatusCode::NoContent)) => {
return Ok(Vec::new());
}
Err(err) => return Err(MALError::Request(err)),
}
};
let root: Element = resp.parse().map_err(MALError::Minidom)?;
let mut entries = Vec::new();
for child in root.children() {
let entry = E::Info::parse_search_result(child).map_err(MALError::List)?;
entries.push(entry);
}
Ok(entries)
}
pub fn read(&self) -> Result<ListEntries<E>, MALError> {
let resp = Request::List(&self.mal.username, E::list_type())
.send(self.mal)
.map_err(MALError::Request)?;
let root: Element = resp.parse().map_err(MALError::Minidom)?;
let mut children = root.children();
let user_info = {
let elem = children
.next()
.ok_or_else(|| MALError::List(ListError::NoUserInfoFound))?;
UserInfo::from_xml(elem).map_err(MALError::List)?
};
let mut entries = Vec::new();
for child in children {
let entry = E::from_xml(child).map_err(MALError::List)?;
entries.push(entry);
}
Ok(ListEntries { user_info, entries })
}
#[inline]
pub fn add(&self, entry: &mut E) -> Result<(), MALError> {
self.add_id(entry.id(), entry.values_mut())?;
entry.set_last_updated_time();
Ok(())
}
pub fn add_id(&self, id: u32, values: &mut E::Values) -> Result<(), MALError> {
let body = values.generate_xml().map_err(MALError::List)?;
Request::Add(id, E::list_type(), &body)
.send(self.mal)
.map_err(MALError::Request)?;
values.reset_changed_fields();
Ok(())
}
#[inline]
pub fn update(&self, entry: &mut E) -> Result<(), MALError> {
self.update_id(entry.id(), entry.values_mut())?;
entry.set_last_updated_time();
Ok(())
}
pub fn update_id(&self, id: u32, values: &mut E::Values) -> Result<(), MALError> {
let body = values.generate_xml().map_err(MALError::List)?;
Request::Update(id, E::list_type(), &body)
.send(self.mal)
.map_err(MALError::Request)?;
values.reset_changed_fields();
Ok(())
}
#[inline]
pub fn delete(&self, entry: &E) -> Result<(), MALError> {
self.delete_id(entry.id())
}
#[inline]
pub fn delete_id(&self, id: u32) -> Result<(), MALError> {
Request::Delete(id, E::list_type())
.send(self.mal)
.map_err(MALError::Request)?;
Ok(())
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Status {
WatchingOrReading = 1,
Completed,
OnHold,
Dropped,
PlanToWatchOrRead = 6,
}
impl Status {
#[inline]
pub fn from_i32(value: i32) -> Option<Status> {
match value {
1 => Some(Status::WatchingOrReading),
2 => Some(Status::Completed),
3 => Some(Status::OnHold),
4 => Some(Status::Dropped),
6 => Some(Status::PlanToWatchOrRead),
_ => None,
}
}
}
impl Default for Status {
#[inline]
fn default() -> Self {
Status::PlanToWatchOrRead
}
}
impl Display for Status {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Status::WatchingOrReading => write!(f, "watching / reading"),
Status::Completed => write!(f, "completed"),
Status::OnHold => write!(f, "on hold"),
Status::Dropped => write!(f, "dropped"),
Status::PlanToWatchOrRead => write!(f, "plan to watch / read"),
}
}
}
#[derive(Debug, Default, Clone)]
struct ChangeTracker<T: Debug + Default + Clone> {
value: T,
changed: bool,
}
impl<T: Debug + Default + Clone> ChangeTracker<T> {
fn new(value: T) -> ChangeTracker<T> {
ChangeTracker {
value,
changed: false,
}
}
fn set(&mut self, value: T) {
self.value = value;
self.changed = true;
}
}
impl<T: Debug + Default + Clone> From<T> for ChangeTracker<T> {
fn from(value: T) -> Self {
ChangeTracker::new(value)
}
}
fn parse_xml_child<T: FromStr>(elem: &Element, name: &str) -> Result<T, ListError> {
let text = elem.children()
.find(|c| c.name() == name)
.ok_or_else(|| ListError::MissingXMLNode(name.into()))?
.texts()
.next()
.unwrap_or("");
text.parse::<T>()
.map_err(|_| ListError::XMLConversionFailed(name.into()))
}
fn parse_str_date(date: &str) -> Option<NaiveDate> {
if date != "0000-00-00" {
NaiveDate::parse_from_str(date, "%Y-%m-%d").ok()
} else {
None
}
}
fn date_to_str(date: Option<NaiveDate>) -> String {
match date {
Some(date) => date.format("%m%d%Y").to_string(),
None => {
"00000000".into()
}
}
}
fn split_by_delim(string: &str, delim: &str) -> Vec<String> {
string
.split(delim)
.skip_while(|s| s.is_empty())
.map(String::from)
.collect()
}
fn concat_by_delim(tags: &[String], delim: char) -> String {
tags.iter().map(|tag| format!("{}{}", tag, delim)).collect()
}
#[derive(Debug)]
pub struct ListEntries<E: ListEntry> {
pub user_info: E::UserInfo,
pub entries: Vec<E>,
}
pub trait ListEntry
where
Self: Sized,
{
type Info: SeriesInfo;
type Values: EntryValues;
type UserInfo: UserInfo;
#[doc(hidden)]
fn from_xml(xml_elem: &Element) -> Result<Self, ListError>;
#[doc(hidden)]
fn values_mut(&mut self) -> &mut Self::Values;
#[doc(hidden)]
fn set_last_updated_time(&mut self);
#[doc(hidden)]
fn id(&self) -> u32;
#[doc(hidden)]
fn list_type() -> ListType;
}
pub trait SeriesInfo
where
Self: Sized,
{
#[doc(hidden)]
fn parse_search_result(xml_elem: &Element) -> Result<Self, ListError>;
}
pub trait EntryValues {
#[doc(hidden)]
fn generate_xml(&self) -> Result<String, ListError> {
let mut entry = Element::bare("entry");
self.add_changed_values(&mut entry);
let mut buffer = Vec::new();
entry.write_to(&mut buffer).map_err(ListError::Minidom)?;
String::from_utf8(buffer).map_err(ListError::Utf8)
}
#[doc(hidden)]
fn add_changed_values(&self, xml_elem: &mut Element);
#[doc(hidden)]
fn reset_changed_fields(&mut self);
}
pub trait UserInfo
where
Self: Sized,
{
#[doc(hidden)]
fn from_xml(xml_elem: &Element) -> Result<Self, ListError>;
}