mod fetch;
use std::path::{Path, PathBuf};
use std::time::Duration;
use chrono::{DateTime, Utc};
use reqwest::Client;
use crate::article::Article;
use crate::cache::CacheStore;
use crate::config::{Config, FeedEntry};
pub struct ArticleStore {
articles: Vec<Article>,
feeds: Vec<FeedEntry>,
config: Config,
cache: CacheStore,
client: Client,
}
#[derive(Debug, Clone, Default)]
pub struct FilterParams {
pub show_read: bool,
pub from: Option<DateTime<Utc>>,
pub limit: Option<usize>,
}
impl ArticleStore {
pub fn new(feeds: Vec<FeedEntry>, config: Config, data_dir: PathBuf) -> Self {
let client = Client::builder()
.timeout(Duration::from_secs(10))
.connect_timeout(Duration::from_secs(5))
.gzip(true)
.brotli(true)
.build()
.unwrap_or_else(|_| Client::new());
Self::with_client(feeds, config, data_dir, client)
}
pub fn with_client(
feeds: Vec<FeedEntry>,
config: Config,
data_dir: PathBuf,
client: Client,
) -> Self {
Self {
articles: Vec::new(),
feeds,
config,
cache: CacheStore::new(data_dir),
client,
}
}
pub fn feeds(&self) -> &[FeedEntry] {
&self.feeds
}
pub fn config(&self) -> &Config {
&self.config
}
pub fn cache(&self) -> &CacheStore {
&self.cache
}
pub fn data_dir(&self) -> &Path {
self.cache.data_dir()
}
pub fn client(&self) -> &Client {
&self.client
}
pub fn articles(&self) -> &[Article] {
&self.articles
}
pub fn set_articles(&mut self, articles: Vec<Article>) {
self.articles = articles;
}
pub fn take_articles(&mut self) -> Vec<Article> {
std::mem::take(&mut self.articles)
}
pub fn get(&self, index: usize) -> Option<&Article> {
self.articles.get(index)
}
pub fn len(&self) -> usize {
self.articles.len()
}
pub fn is_empty(&self) -> bool {
self.articles.is_empty()
}
pub fn mark_read(&mut self, index: usize) {
if let Some(a) = self.articles.get_mut(index) {
a.read = true;
let cache = self.cache.clone();
let feed_url = a.feed_url.clone();
let url = a.url.clone();
tokio::task::spawn_blocking(move || {
let _ = cache.set_read_status(&feed_url, &url, true);
});
}
}
pub fn toggle_read(&mut self, index: usize) -> bool {
if let Some(a) = self.articles.get_mut(index) {
a.read = !a.read;
let new_read = a.read;
let cache = self.cache.clone();
let feed_url = a.feed_url.clone();
let url = a.url.clone();
tokio::task::spawn_blocking(move || {
let _ = cache.set_read_status(&feed_url, &url, new_read);
});
new_read
} else {
false
}
}
pub fn query(&self, params: &FilterParams) -> Vec<usize> {
let mut result: Vec<usize> = self
.articles
.iter()
.enumerate()
.filter(|(_, a)| params.show_read || !a.read)
.filter(|(_, a)| match params.from {
Some(from) => a.published.is_none_or(|dt| dt >= from),
None => true,
})
.map(|(i, _)| i)
.collect();
if let Some(limit) = params.limit {
result.truncate(limit);
}
result
}
pub fn query_articles(&self, params: &FilterParams) -> Vec<Article> {
self.query(params)
.into_iter()
.filter_map(|i| self.articles.get(i).cloned())
.collect()
}
}