1use std::collections::HashMap;
4use std::time::{Duration, Instant};
5
6use serde_json;
7
8use crate::config::Config;
9#[cfg(test)]
10use crate::config::{AIConfig, FormatsConfig, GeneralConfig, SyncConfig};
11
12struct CacheEntry {
14 value: serde_json::Value,
15 created_at: Instant,
16 ttl: Duration,
17}
18
19pub struct ConfigCache {
21 entries: HashMap<String, CacheEntry>,
22 default_ttl: Duration,
23}
24
25impl ConfigCache {
26 pub fn new() -> Self {
28 Self {
29 entries: HashMap::new(),
30 default_ttl: Duration::from_secs(300),
31 }
32 }
33
34 pub fn get<T>(&self, key: &str) -> Option<T>
36 where
37 T: serde::de::DeserializeOwned,
38 {
39 let entry = self.entries.get(key)?;
40 if entry.created_at.elapsed() > entry.ttl {
41 return None;
42 }
43 serde_json::from_value(entry.value.clone()).ok()
44 }
45
46 pub fn set<T>(&mut self, key: String, value: T, ttl: Option<Duration>)
48 where
49 T: serde::Serialize,
50 {
51 let json_value = serde_json::to_value(value).unwrap();
52 let entry = CacheEntry {
53 value: json_value,
54 created_at: Instant::now(),
55 ttl: ttl.unwrap_or(self.default_ttl),
56 };
57 self.entries.insert(key, entry);
58 }
59
60 pub fn update(&mut self, config: &Config) {
62 self.set("full_config".to_string(), config, None);
63 self.set("ai_config".to_string(), &config.ai, None);
64 self.set("formats_config".to_string(), &config.formats, None);
65 self.set("sync_config".to_string(), &config.sync, None);
66 self.set("general_config".to_string(), &config.general, None);
67 }
68
69 pub fn clear(&mut self) {
71 self.entries.clear();
72 }
73
74 pub fn cleanup_expired(&mut self) {
76 self.entries
77 .retain(|_, entry| entry.created_at.elapsed() <= entry.ttl);
78 }
79}
80
81impl Default for ConfigCache {
82 fn default() -> Self {
83 Self::new()
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use std::thread::sleep;
91 use std::time::Duration;
92
93 #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
94 struct Dummy {
95 x: u32,
96 }
97
98 #[test]
99 fn test_set_get() {
100 let mut cache = ConfigCache::new();
101 cache.set("dummy".to_string(), Dummy { x: 42 }, None);
102 let value: Option<Dummy> = cache.get("dummy");
103 assert_eq!(value, Some(Dummy { x: 42 }));
104 }
105
106 #[test]
107 fn test_ttl_expiration() {
108 let mut cache = ConfigCache::new();
109 cache.set(
110 "dummy".to_string(),
111 Dummy { x: 42 },
112 Some(Duration::from_millis(10)),
113 );
114 sleep(Duration::from_millis(20));
115 let value: Option<Dummy> = cache.get("dummy");
116 assert!(value.is_none());
117 }
118
119 #[test]
120 fn test_clear_and_cleanup() {
121 let mut cache = ConfigCache::new();
122 cache.set("a".to_string(), Dummy { x: 1 }, None);
123 cache.set(
124 "b".to_string(),
125 Dummy { x: 2 },
126 Some(Duration::from_secs(0)),
127 );
128 cache.cleanup_expired();
129 assert!(cache.get::<Dummy>("b").is_none());
130 assert!(cache.get::<Dummy>("a").is_some());
131 cache.clear();
132 assert!(cache.get::<Dummy>("a").is_none());
133 }
134
135 #[test]
136 fn test_update() {
137 let mut cache = ConfigCache::new();
138 let config = Config::default();
139 cache.update(&config);
140 assert!(cache.get::<Config>("full_config").is_some());
141 assert!(cache.get::<AIConfig>("ai_config").is_some());
142 assert!(cache.get::<FormatsConfig>("formats_config").is_some());
143 assert!(cache.get::<SyncConfig>("sync_config").is_some());
144 assert!(cache.get::<GeneralConfig>("general_config").is_some());
145 }
146}