summaryrefslogtreecommitdiff
path: root/src/manager.rs
blob: 839c8dcd4cd1d2c22f50f560a8dbc588f4ce5588 (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
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 {
    pub async fn add_feed(&mut self, url: &str) -> Result<PullStatus, 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 mut feed = Feed::new(link.clone());

        let status = feed
            .pull()
            .await
            .map_err(|pull_err| Error::Pull(link, pull_err))?;

        // add new feed
        self.feeds.push(feed);

        Ok(status)
    }

    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> {
        self.feeds.iter()
    }

    pub async 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)?;

        Ok(StoreStatus::new(self.feeds.len()))
    }

    pub async 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 })
    }
}

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,
        }
    }
}