1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
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<Feed>,
}
impl Manager {
fn add_feed(&mut self, url: &str) -> Result<&mut Feed, Error> {
let link = Url::parse(&url).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(self.feeds.last_mut().unwrap())
}
pub async fn add_feed_and_pull(&mut self, url: &str) -> Result<PullStatus, Error> {
let feed = self.add_feed(url)?;
feed.pull()
.await
.map_err(|pull_err| Error::Pull(feed.link.clone(), pull_err))
}
pub async fn add_feeds_and_pull(&mut self, urls: &[&str]) -> Vec<Result<PullStatus, Error>> {
let add_results = urls.into_iter().map(|url| self.add_feed(&url));
self.pull()
}
pub async fn pull(&mut self) -> Vec<Result<PullStatus, PullError>> {
futures::future::join_all(self.feeds.iter_mut().map(Feed::pull)).await
}
pub fn list_entries(&self) -> impl Iterator<Item = &Entry> {
EntryIterator {
all_entries: self.feeds.iter().map(Feed::entries).collect(),
}
}
pub fn list_feeds(&self) -> impl Iterator<Item = &Feed> {
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<StoreStatus, IOError> {
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<Self, IOError> {
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<Self::Item> {
let mut min_index = None;
let mut last_date = DateTime::<Utc>::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,
}
}
}
|