use crate::{ error::{AddError, Error, IOError, PullError}, feed::{Entry, Feed}, status::{PullStatus, StoreStatus}, }; use chrono::prelude::*; use url::Url; #[derive(Default)] pub struct Manager { feeds: Vec, } impl Manager { fn add_feed>(&mut self, url: P) -> Result<(), Error> { let link = Url::parse(url.as_ref()).map_err(|e| AddError::InvalidUrl(e.to_string()))?; // check if this feed is already present if self.feeds.iter().any(|f| f.link == link) { return Err(AddError::DuplicateLink.into()); } // construct a new feed let feed = Feed::new(link.clone()); // add new feed self.feeds.push(feed); Ok(()) } pub async fn add_feed_and_pull(&mut self, url: &str) -> Result { self.add_feed(url)?; let feed = self.feeds.last_mut().unwrap(); feed.pull() .await .map_err(|pull_err| Error::Pull(feed.link.clone(), pull_err)) } pub async fn add_feeds_and_pull>( &mut self, urls: &[P], ) -> Vec> { for url in urls { // TODO: handle this error self.add_feed(&url); } futures::future::join_all(self.feeds.iter_mut().map(|feed| async { feed.pull() .await .map_err(|pull_err| Error::Pull(feed.link.clone(), pull_err)) })) .await } pub async fn pull(&mut self) -> Vec> { futures::future::join_all(self.feeds.iter_mut().map(Feed::pull)).await } pub fn list_entries(&self) -> impl Iterator { EntryIterator { all_entries: self.feeds.iter().map(Feed::entries).collect(), } } pub fn list_feeds(&self) -> impl Iterator { let mut ordered: Vec<&Feed> = self.feeds.iter().collect(); ordered.sort_by(|a, b| b.last_updated().cmp(&a.last_updated())); ordered.into_iter() } pub fn store(&self) -> Result { let path = crate::dirs::store_path().ok_or(IOError::MissingStorePath)?; let content = serde_yaml::to_string(&self.feeds)?; std::fs::write(&path, content)?; let count = self.feeds.len(); let location = path; Ok(StoreStatus::new(count, location)) } pub fn load() -> Result { let path = crate::dirs::store_path().ok_or(IOError::MissingStorePath)?; let content = std::fs::read_to_string(path)?; let feeds = serde_yaml::from_str(&content)?; Ok(Self { feeds }) } } // an iterator over a combined list of feeds (assumes each feed is already a sorted list of // entries) struct EntryIterator<'e> { all_entries: Vec<&'e [Entry]>, } impl<'e> Iterator for EntryIterator<'e> { type Item = &'e Entry; fn next(&mut self) -> Option { let mut min_index = None; let mut last_date = DateTime::::MIN_UTC; for (idx, latest_entry) in self .all_entries .iter() .map(|entries| entries.first()) .enumerate() { if let Some(entry) = latest_entry { if last_date < entry.published { last_date = entry.published; min_index = Some(idx); } } } match min_index { Some(idx) => { let entries = self.all_entries.get_mut(idx).unwrap(); let e = &entries[0]; if entries.len() > 1 { *entries = &entries[1..]; } else { self.all_entries.remove(idx); } Some(e) } None => None, } } }