use std::{ fs, path, borrow::Cow, collections::{ BTreeMap, HashMap, }, }; #[derive(Debug, Default, serde::Serialize, serde::Deserialize)] pub(crate) struct Specification<'a> { files: HashMap, Cow<'a, path::Path>>, /// This is a collection of episodes, where each entry contains a `Vec` of /// episodes to allow for the possibility that multiple episodes have the /// same timestamp. feed: BTreeMap>>, pub(in crate) image_url: Option>, } impl<'a> Specification<'a> { /// Reads from the specification file from a given path, or gives a default /// if the file doesn't exist. pub(crate) fn read_from_with_default( path: &path::Path ) -> Result { Ok(if path.is_file() { toml::from_str(&fs::read_to_string(&path)?[..])? } else { Specification::default() }) } /// Reads from the specification file from a given path. pub(crate) fn read_from( path: &path::Path ) -> Result { Ok(if path.is_file() { toml::from_str(&fs::read_to_string(&path)?[..])? } else { anyhow::bail!("could not find specification for the desired podcast") }) } /// Writes the specification to the specific file path. pub(crate) fn write_to(&self, path: &path::Path) -> Result<(), anyhow::Error> { Ok(fs::write(path, toml::to_string(self)?.as_bytes())?) } pub(crate) fn feed_iter(&self) -> impl Iterator>)> { self.feed.iter() } pub(crate) fn feed_iter_mut(&mut self) -> impl Iterator>)> { self.feed.iter_mut() } pub(crate) fn path_from_id(&self, id: &str) -> Option<&path::Path> { self.files.get(id).map(|v| &**v) } pub(crate) fn into_feed_and_files( self ) -> ( BTreeMap>>, HashMap, Cow<'a, path::Path>>, ) { (self.feed, self.files) } pub(crate) fn insert_into_files( &mut self, id: impl Into>, path: impl Into>, ) -> Option> { self.files.insert( id.into(), path.into() ) } pub(crate) fn insert_into_feed( &mut self, published: chrono::NaiveDateTime, episode: Episode<'a> ) { match self.feed.get_mut(&published) { Some(existing) => { existing.push(episode) }, None => { self.feed.insert( published, vec![episode], ); } } } pub(crate) fn files_and_feed_mut( &mut self, ) -> ( &mut BTreeMap>>, &HashMap, Cow<'a, path::Path>>, ) { (&mut self.feed, &self.files) } } #[derive(Debug, serde::Serialize, serde::Deserialize)] pub (crate) struct Episode<'a> { /// Episode title. title: Cow<'a, str>, /// Show notes pulled from description or summary tag. show_notes: Option>, /// This is the GUID or the URL if the GUID is not present. id: Cow<'a, str>, /// If the episode exists in the latest version of the feed. pub(crate) current: bool, /// Flag to keep track of which episodes have been listened to. #[serde(default)] pub(crate) listened: bool, } impl<'a> Episode<'a> { pub(crate) fn new_downloaded( title: impl Into>, show_notes: Option>, id: impl Into>, ) -> Self { Self { title: title.into(), show_notes, id: id.into(), current: true, listened: false, } } pub(crate) fn title(&self) -> &str { self.title.as_ref() } pub(crate) fn id(&self) -> &str { &self.id } }