1use std::collections::HashMap;
4use std::path::Path;
5use std::time::SystemTime;
6
7use crate::model::Row;
8
9#[derive(Debug)]
11struct CachedRow {
12 mtime: SystemTime,
13 row: Row,
14}
15
16#[derive(Debug)]
18pub struct TableCache {
19 rows: HashMap<String, CachedRow>,
20 table_mtime: Option<SystemTime>,
21}
22
23impl TableCache {
24 pub fn new() -> Self {
25 TableCache {
26 rows: HashMap::new(),
27 table_mtime: None,
28 }
29 }
30
31 pub fn get(&self, path: &str, current_mtime: SystemTime) -> Option<&Row> {
33 self.rows.get(path).and_then(|cached| {
34 if cached.mtime == current_mtime {
35 Some(&cached.row)
36 } else {
37 None
38 }
39 })
40 }
41
42 pub fn put(&mut self, path: String, mtime: SystemTime, row: Row) {
44 self.rows.insert(path, CachedRow { mtime, row });
45 }
46
47 pub fn remove(&mut self, path: &str) {
49 self.rows.remove(path);
50 }
51
52 pub fn is_stale(&self, table_dir: &Path) -> bool {
54 let current = dir_mtime(table_dir);
55 match (self.table_mtime, current) {
56 (Some(cached), Some(now)) => cached != now,
57 (None, _) => true, (_, None) => true, }
60 }
61
62 pub fn set_table_mtime(&mut self, table_dir: &Path) {
64 self.table_mtime = dir_mtime(table_dir);
65 }
66
67 pub fn invalidate_all(&mut self) {
69 self.rows.clear();
70 self.table_mtime = None;
71 }
72
73 pub fn len(&self) -> usize {
75 self.rows.len()
76 }
77
78 pub fn is_empty(&self) -> bool {
80 self.rows.is_empty()
81 }
82
83 pub fn cached_paths(&self) -> Vec<&str> {
85 self.rows.keys().map(|s| s.as_str()).collect()
86 }
87}
88
89impl Default for TableCache {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95fn dir_mtime(path: &Path) -> Option<SystemTime> {
96 std::fs::metadata(path)
97 .and_then(|m| m.modified())
98 .ok()
99}
100
101pub fn file_mtime(path: &Path) -> Option<SystemTime> {
103 std::fs::metadata(path)
104 .and_then(|m| m.modified())
105 .ok()
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::model::Value;
112 use std::time::Duration;
113
114 #[test]
115 fn test_cache_hit_and_miss() {
116 let mut cache = TableCache::new();
117 let mtime = SystemTime::UNIX_EPOCH + Duration::from_secs(1000);
118 let row = Row::from([
119 ("path".into(), Value::String("test.md".into())),
120 ("title".into(), Value::String("Hello".into())),
121 ]);
122
123 cache.put("test.md".into(), mtime, row);
124
125 assert!(cache.get("test.md", mtime).is_some());
127
128 let later = mtime + Duration::from_secs(1);
130 assert!(cache.get("test.md", later).is_none());
131 }
132
133 #[test]
134 fn test_remove() {
135 let mut cache = TableCache::new();
136 let mtime = SystemTime::UNIX_EPOCH + Duration::from_secs(1000);
137 let row = Row::from([("path".into(), Value::String("test.md".into()))]);
138 cache.put("test.md".into(), mtime, row);
139 assert_eq!(cache.len(), 1);
140
141 cache.remove("test.md");
142 assert_eq!(cache.len(), 0);
143 }
144
145 #[test]
146 fn test_invalidate_all() {
147 let mut cache = TableCache::new();
148 let mtime = SystemTime::UNIX_EPOCH;
149 cache.put("a.md".into(), mtime, Row::new());
150 cache.put("b.md".into(), mtime, Row::new());
151 assert_eq!(cache.len(), 2);
152
153 cache.invalidate_all();
154 assert!(cache.is_empty());
155 }
156}