summaryrefslogtreecommitdiff
path: root/src/manager.rs
blob: 83e8c1b2aada84f47aaf71aed3323d8387996555 (plain)
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
122
123
124
125
126
127
128
129
130
131
132
133
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<P: AsRef<str>>(&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<PullStatus, Error> {
        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<P: AsRef<str>>(
        &mut self,
        urls: &[P],
    ) -> Vec<Result<PullStatus, Error>> {
        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<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,
        }
    }
}