use std::cell::RefCell;
use std::io::BufRead;
use std::mem;
use std::time::Duration;
use mediatype::{MediaTypeBuf, names};
use crate::model::{Entry, Image, MediaCommunity, MediaContent, MediaCredit, MediaObject, MediaObjectSource, MediaRating, MediaText, MediaThumbnail, Text};
use crate::parser::util::{if_ok_then_some, if_some_then, parse_npt};
use crate::parser::{ParseErrorKind, ParseFeedError, ParseFeedResult, util};
use crate::xml::{Element, NS};
pub(crate) struct MediaRssState {
root: RefCell<Option<Scope>>,
}
#[derive(Default)]
struct Scope {
title: Option<Text>,
community: Option<MediaCommunity>,
credits: Vec<MediaCredit>,
description: Option<Text>,
rating: Option<MediaRating>,
texts: Vec<MediaText>,
thumbnails: Vec<MediaThumbnail>,
pending: Vec<MediaContent>,
children: Vec<Scope>,
}
impl MediaRssState {
pub(crate) fn entry_begin(&self) {
assert!(self.root.borrow().is_none());
self.root.borrow_mut().replace(Scope::default());
}
pub(crate) fn entry_end(&self, entry: &mut Entry) {
let scope = self.root.replace(None).unwrap();
emit_media_objects(scope, entry);
}
pub(crate) fn handle_entry_mediarss_element<R: BufRead>(&self, element: Element<R>) -> ParseFeedResult<()> {
if let Some(scope) = self.root.borrow_mut().as_mut() {
inner_handle_mediarss_element(element, scope)?;
Ok(())
} else {
Err(ParseFeedError::ParseError(ParseErrorKind::IllegalState {
position: element.buffer_pos,
reference: "scope was not setup during mediarss entry parsing".to_string(),
}))
}
}
pub(crate) fn new() -> MediaRssState {
MediaRssState { root: RefCell::new(None) }
}
}
fn emit_media_objects(mut scope: Scope, entry: &mut Entry) {
if !scope.pending.is_empty() {
let mut media_obj = MediaObject::new(MediaObjectSource::MediaRSS);
media_obj.title = scope.title.clone();
media_obj.description = scope.description.clone();
media_obj.thumbnails = scope.thumbnails.clone();
media_obj.community = scope.community.clone();
media_obj.thumbnails = scope.thumbnails.clone();
media_obj.texts = scope.texts.clone();
media_obj.credits = scope.credits.clone();
let mut pending = mem::take(&mut scope.pending);
if scope.rating.is_some() {
pending.iter_mut().for_each(|content| {
if content.rating.is_none() {
content.rating = scope.rating.clone();
}
});
}
media_obj.content = pending;
entry.media.push(media_obj);
}
let children = mem::take(&mut scope.children);
children.into_iter().for_each(|mut child| {
merge_parent_scope_into_child(&scope, &mut child);
emit_media_objects(child, entry);
});
}
fn inner_handle_mediarss_element<R: BufRead>(element: Element<R>, scope: &mut Scope) -> ParseFeedResult<()> {
if let (NS::MediaRSS, "group") = element.ns_and_tag() {
let mut nested = Scope::default();
for child in element.children() {
let child = child?;
inner_handle_mediarss_element(child, &mut nested)?;
}
scope.children.push(nested);
} else {
match element.ns_and_tag() {
(NS::MediaRSS, "title") => scope.title = handle_text(element)?,
(NS::MediaRSS, "content") => handle_media_content(element, scope)?,
(NS::MediaRSS, "thumbnail") => if_some_then(handle_media_thumbnail(element), |thumbnail| scope.thumbnails.push(thumbnail)),
(NS::MediaRSS, "description") => scope.description = handle_text(element)?,
(NS::MediaRSS, "community") => scope.community = handle_media_community(element)?,
(NS::MediaRSS, "credit") => if_some_then(handle_media_credit(element), |credit| scope.credits.push(credit)),
(NS::MediaRSS, "text") => if_some_then(handle_media_text(element), |text| scope.texts.push(text)),
(NS::MediaRSS, "rating") => scope.rating = handle_media_rating(element),
_ => {}
}
}
Ok(())
}
fn merge_parent_scope_into_child(parent: &Scope, child: &mut Scope) {
if child.title.is_none() {
child.title = parent.title.clone();
}
if child.community.is_none() {
child.community = parent.community.clone();
}
if child.description.is_none() {
child.description = parent.description.clone();
}
if child.rating.is_none() {
child.rating = parent.rating.clone();
}
if child.credits.is_empty() {
child.credits = parent.credits.clone();
}
if child.texts.is_empty() {
child.texts = parent.texts.clone();
}
if child.thumbnails.is_empty() {
child.thumbnails = parent.thumbnails.clone();
}
}
fn handle_media_community<R: BufRead>(element: Element<R>) -> ParseFeedResult<Option<MediaCommunity>> {
let mut community = MediaCommunity::new();
for child in element.children() {
let child = child?;
match child.ns_and_tag() {
(NS::MediaRSS, "starRating") => {
for attr in &child.attributes {
match attr.name.as_str() {
"average" => if_ok_then_some(attr.value.parse::<f64>(), |v| community.stars_avg = v),
"count" => if_ok_then_some(attr.value.parse::<u64>(), |v| community.stars_count = v),
"min" => if_ok_then_some(attr.value.parse::<u64>(), |v| community.stars_min = v),
"max" => if_ok_then_some(attr.value.parse::<u64>(), |v| community.stars_max = v),
_ => {}
}
}
}
(NS::MediaRSS, "statistics") => {
for attr in &child.attributes {
match attr.name.as_str() {
"views" => if_ok_then_some(attr.value.parse::<u64>(), |v| community.stats_views = v),
"favorites" => if_ok_then_some(attr.value.parse::<u64>(), |v| community.stats_favorites = v),
_ => {}
}
}
}
_ => {}
}
}
Ok(Some(community))
}
fn handle_media_content<R: BufRead>(element: Element<R>, media_obj: &mut Scope) -> ParseFeedResult<()> {
let mut content = MediaContent::new();
for attr in &element.attributes {
match attr.name.as_str() {
"url" => content.url = util::parse_uri(&attr.value, element.xml_base.as_ref()),
"type" => if_ok_then_some(attr.value.parse::<MediaTypeBuf>(), |v| content.content_type = v),
"width" => if_ok_then_some(attr.value.parse::<u32>(), |v| content.width = v),
"height" => if_ok_then_some(attr.value.parse::<u32>(), |v| content.height = v),
"fileSize" => if_ok_then_some(attr.value.parse::<u64>(), |v| content.size = v),
"duration" => if_ok_then_some(attr.value.parse::<u64>(), |v| content.duration = v.map(Duration::from_secs)),
_ => {}
}
}
for child in element.children() {
let child = child?;
match child.ns_and_tag() {
(NS::MediaRSS, "player") => {
if let Some(player_url) = child.attr_value("url") {
let player_url = util::parse_uri(&player_url, element.xml_base.as_ref());
if player_url.is_some() {
content.url = player_url;
}
}
}
(NS::MediaRSS, "rating") => content.rating = handle_media_rating(child),
(NS::MediaRSS, "title") if media_obj.title.is_none() => media_obj.title = handle_text(child)?,
(NS::MediaRSS, "description") if media_obj.description.is_none() => media_obj.description = handle_text(child)?,
(NS::MediaRSS, "text") => if_some_then(handle_media_text(child), |text| media_obj.texts.push(text)),
(NS::MediaRSS, "credit") => if_some_then(handle_media_credit(child), |credit| media_obj.credits.push(credit)),
(NS::MediaRSS, _) => inner_handle_mediarss_element(child, media_obj)?,
_ => {}
}
}
if content.url.is_some() {
media_obj.pending.push(content);
}
Ok(())
}
fn handle_media_credit<R: BufRead>(element: Element<R>) -> Option<MediaCredit> {
element.child_as_text().map(MediaCredit::new)
}
fn handle_media_rating<R: BufRead>(element: Element<R>) -> Option<MediaRating> {
let scheme = element.attr_value("scheme").unwrap_or_else(|| "urn:simple".into());
element.child_as_text().map(|rating| MediaRating::new(rating).urn(scheme.as_str()))
}
fn handle_media_text<R: BufRead>(element: Element<R>) -> Option<MediaText> {
let mut start_time = None;
let mut end_time = None;
let mut mime = None;
for attr in &element.attributes {
match attr.name.as_str() {
"start" => if_some_then(parse_npt(&attr.value), |npt| start_time = Some(npt)),
"end" => if_some_then(parse_npt(&attr.value), |npt| end_time = Some(npt)),
"type" => {
mime = match attr.value.as_str() {
"plain" => Some(MediaTypeBuf::new(names::TEXT, names::PLAIN)),
"html" => Some(MediaTypeBuf::new(names::TEXT, names::HTML)),
_ => None,
}
}
_ => {}
}
}
element.child_as_text().map(|t| {
let mut text = Text::new(t);
text.content_type = mime.unwrap_or(MediaTypeBuf::new(names::TEXT, names::PLAIN));
let mut media_text = MediaText::new(text);
media_text.start_time = start_time;
media_text.end_time = end_time;
media_text
})
}
fn handle_media_thumbnail<R: BufRead>(element: Element<R>) -> Option<MediaThumbnail> {
let mut url = None;
let mut width = None;
let mut height = None;
let mut time = None;
for attr in &element.attributes {
match attr.name.as_str() {
"url" => url = Some(attr.value.clone()),
"width" => if_ok_then_some(attr.value.parse::<u32>(), |v| width = v),
"height" => if_ok_then_some(attr.value.parse::<u32>(), |v| height = v),
"time" => if_some_then(parse_npt(&attr.value), |npt| time = Some(npt)),
_ => {}
}
}
if let Some(url) = url {
let mut image = Image::new(url);
image.width = width;
image.height = height;
let mut thumbnail = MediaThumbnail::new(image);
thumbnail.time = time;
Some(thumbnail)
} else {
None
}
}
fn handle_text<R: BufRead>(element: Element<R>) -> ParseFeedResult<Option<Text>> {
let type_attr = element.attributes.iter().find(|a| &a.name == "type").map_or("plain", |a| a.value.as_str());
let mime = match type_attr {
"plain" => Ok(MediaTypeBuf::new(names::TEXT, names::PLAIN)),
"html" => Ok(MediaTypeBuf::new(names::TEXT, names::HTML)),
_ => Err(ParseFeedError::ParseError(ParseErrorKind::UnknownMimeType {
position: element.buffer_pos,
mime: type_attr.into(),
})),
}?;
element
.child_as_text()
.map(|content| {
let mut text = Text::new(content);
text.content_type = mime;
Some(text)
})
.ok_or(ParseFeedError::ParseError(ParseErrorKind::MissingContent {
position: element.buffer_pos,
reference: "text",
}))
}