use std::collections::HashMap;
use std::path::Path;
use std::time::SystemTime;
use crate::model::Row;
#[derive(Debug)]
struct CachedRow {
mtime: SystemTime,
row: Row,
}
#[derive(Debug)]
pub struct TableCache {
rows: HashMap<String, CachedRow>,
table_mtime: Option<SystemTime>,
}
impl TableCache {
pub fn new() -> Self {
TableCache {
rows: HashMap::new(),
table_mtime: None,
}
}
pub fn get(&self, path: &str, current_mtime: SystemTime) -> Option<&Row> {
self.rows.get(path).and_then(|cached| {
if cached.mtime == current_mtime {
Some(&cached.row)
} else {
None
}
})
}
pub fn put(&mut self, path: String, mtime: SystemTime, row: Row) {
self.rows.insert(path, CachedRow { mtime, row });
}
pub fn remove(&mut self, path: &str) {
self.rows.remove(path);
}
pub fn is_stale(&self, table_dir: &Path) -> bool {
let current = dir_mtime(table_dir);
match (self.table_mtime, current) {
(Some(cached), Some(now)) => cached != now,
(None, _) => true, (_, None) => true, }
}
pub fn set_table_mtime(&mut self, table_dir: &Path) {
self.table_mtime = dir_mtime(table_dir);
}
pub fn invalidate_all(&mut self) {
self.rows.clear();
self.table_mtime = None;
}
pub fn len(&self) -> usize {
self.rows.len()
}
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
pub fn cached_paths(&self) -> Vec<&str> {
self.rows.keys().map(|s| s.as_str()).collect()
}
}
impl Default for TableCache {
fn default() -> Self {
Self::new()
}
}
fn dir_mtime(path: &Path) -> Option<SystemTime> {
std::fs::metadata(path)
.and_then(|m| m.modified())
.ok()
}
pub fn file_mtime(path: &Path) -> Option<SystemTime> {
std::fs::metadata(path)
.and_then(|m| m.modified())
.ok()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::Value;
use std::time::Duration;
#[test]
fn test_cache_hit_and_miss() {
let mut cache = TableCache::new();
let mtime = SystemTime::UNIX_EPOCH + Duration::from_secs(1000);
let row = Row::from([
("path".into(), Value::String("test.md".into())),
("title".into(), Value::String("Hello".into())),
]);
cache.put("test.md".into(), mtime, row);
assert!(cache.get("test.md", mtime).is_some());
let later = mtime + Duration::from_secs(1);
assert!(cache.get("test.md", later).is_none());
}
#[test]
fn test_remove() {
let mut cache = TableCache::new();
let mtime = SystemTime::UNIX_EPOCH + Duration::from_secs(1000);
let row = Row::from([("path".into(), Value::String("test.md".into()))]);
cache.put("test.md".into(), mtime, row);
assert_eq!(cache.len(), 1);
cache.remove("test.md");
assert_eq!(cache.len(), 0);
}
#[test]
fn test_invalidate_all() {
let mut cache = TableCache::new();
let mtime = SystemTime::UNIX_EPOCH;
cache.put("a.md".into(), mtime, Row::new());
cache.put("b.md".into(), mtime, Row::new());
assert_eq!(cache.len(), 2);
cache.invalidate_all();
assert!(cache.is_empty());
}
}