use crate::feed_api::{FeedApiError, FeedApiResult};
use crate::models::FeedID;
use chrono::{DateTime, TimeDelta, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::Read;
use std::path::{Path, PathBuf};
static FILE_NAME: &str = "local_rss_cache.json";
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LocalRSSCache {
#[serde(default)]
etags: HashMap<FeedID, String>,
#[serde(default)]
cache_control: HashMap<FeedID, DateTime<Utc>>,
#[serde(skip_serializing)]
#[serde(skip_deserializing)]
path: PathBuf,
}
impl LocalRSSCache {
pub fn load(config_dir: &Path) -> FeedApiResult<Self> {
let path = config_dir.join(FILE_NAME);
if path.as_path().exists() {
let mut contents = String::new();
tracing::info!(?path, "Attempting to open local cache file");
let mut file = File::open(&path).map_err(move |error| {
tracing::error!(%error, "Failed to load local cache file");
FeedApiError::IO(error)
})?;
file.read_to_string(&mut contents).map_err(move |error| {
tracing::error!(%error, "Reading content of file failed");
FeedApiError::IO(error)
})?;
let mut cache: LocalRSSCache = serde_json::from_str(&contents)?;
cache.path = path;
return Ok(cache);
}
tracing::info!(?path, "Local cache file does not exist. Returning empty cache");
Ok(LocalRSSCache {
etags: HashMap::new(),
cache_control: HashMap::new(),
path,
})
}
pub fn get_etag(&self, feed_id: &FeedID) -> Option<String> {
self.etags.get(feed_id).cloned()
}
pub fn set_etag(&mut self, feed_id: &FeedID, etag: Option<String>) {
if let Some(etag) = etag {
self.etags.insert(feed_id.clone(), etag);
} else {
self.etags.remove(feed_id);
}
}
pub fn get_cache_control(&self, feed_id: &FeedID) -> Option<DateTime<Utc>> {
self.cache_control.get(feed_id).copied()
}
pub fn set_cache_control(&mut self, feed_id: &FeedID, max_age: Option<u64>) {
if let Some(max_age) = max_age {
self.cache_control
.insert(feed_id.clone(), Utc::now() + TimeDelta::seconds(max_age as i64));
} else {
self.cache_control.remove(feed_id);
}
}
pub fn write(&self) -> FeedApiResult<()> {
let data = serde_json::to_string_pretty(self)?;
fs::write(&self.path, data).map_err(|error| {
tracing::error!(?self.path, %error, "Failed to write local cache file to");
FeedApiError::IO(error)
})?;
Ok(())
}
pub fn delete(&self) -> FeedApiResult<()> {
fs::remove_file(&self.path)?;
Ok(())
}
}