1use std::fs;
5use std::path::{Path, PathBuf};
6use std::time::{Duration, SystemTime};
7
8use serde::de::DeserializeOwned;
9use serde::Serialize;
10
11use super::error::{Result, SpsError};
12
13const CACHE_SUBDIR: &str = "sps";
17const CACHE_TTL: Duration = Duration::from_secs(24 * 60 * 60); pub struct Cache {
22 cache_dir: PathBuf,
23}
24
25impl Cache {
26 pub fn new(cache_dir: &Path) -> Result<Self> {
27 if !cache_dir.exists() {
28 fs::create_dir_all(cache_dir)?;
29 }
30
31 Ok(Self {
32 cache_dir: cache_dir.to_path_buf(),
33 })
34 }
35
36 pub fn get_dir(&self) -> &Path {
38 &self.cache_dir
39 }
40
41 pub fn store_raw(&self, filename: &str, data: &str) -> Result<()> {
43 let path = self.cache_dir.join(filename);
44 tracing::debug!("Saving raw data to cache file: {:?}", path);
45 fs::write(&path, data)?;
46 Ok(())
47 }
48
49 pub fn load_raw(&self, filename: &str) -> Result<String> {
51 let path = self.cache_dir.join(filename);
52 tracing::debug!("Loading raw data from cache file: {:?}", path);
53
54 if !path.exists() {
55 return Err(SpsError::Cache(format!(
56 "Cache file {filename} does not exist"
57 )));
58 }
59
60 fs::read_to_string(&path).map_err(|e| SpsError::Cache(format!("IO error: {e}")))
61 }
62
63 pub fn is_cache_valid(&self, filename: &str) -> Result<bool> {
65 let path = self.cache_dir.join(filename);
66 if !path.exists() {
67 return Ok(false);
68 }
69
70 let metadata = fs::metadata(&path)?;
71 let modified_time = metadata.modified()?;
72 let age = SystemTime::now()
73 .duration_since(modified_time)
74 .map_err(|e| SpsError::Cache(format!("System time error: {e}")))?;
75
76 Ok(age <= CACHE_TTL)
77 }
78
79 pub fn clear_file(&self, filename: &str) -> Result<()> {
81 let path = self.cache_dir.join(filename);
82 if path.exists() {
83 fs::remove_file(&path)?;
84 }
85 Ok(())
86 }
87
88 pub fn clear_all(&self) -> Result<()> {
90 if self.cache_dir.exists() {
91 fs::remove_dir_all(&self.cache_dir)?;
92 fs::create_dir_all(&self.cache_dir)?;
93 }
94 Ok(())
95 }
96}
97
98pub fn get_cache_dir() -> Result<PathBuf> {
101 let base_cache_dir = dirs::cache_dir()
102 .ok_or_else(|| SpsError::Cache("Could not determine system cache directory".to_string()))?;
103 let app_cache_dir = base_cache_dir.join(CACHE_SUBDIR);
104
105 if !app_cache_dir.exists() {
106 tracing::debug!("Creating cache directory at {:?}", app_cache_dir);
107 fs::create_dir_all(&app_cache_dir)?;
108 }
109 Ok(app_cache_dir)
110}
111
112fn get_cache_path(filename: &str) -> Result<PathBuf> {
114 Ok(get_cache_dir()?.join(filename))
115}
116
117pub fn save_to_cache<T: Serialize>(filename: &str, data: &T) -> Result<()> {
120 let path = get_cache_path(filename)?;
121 tracing::debug!("Saving data to cache file: {:?}", path);
122 let file = fs::File::create(&path)?;
123 serde_json::to_writer_pretty(file, data)?;
125 Ok(())
126}
127
128pub fn load_from_cache<T: DeserializeOwned>(filename: &str) -> Result<T> {
131 let path = get_cache_path(filename)?;
132 tracing::debug!("Attempting to load from cache file: {:?}", path);
133
134 if !path.exists() {
135 tracing::debug!("Cache file not found.");
136 return Err(SpsError::Cache("Cache file does not exist".to_string()));
137 }
138
139 let metadata = fs::metadata(&path)?;
141 let modified_time = metadata.modified()?;
142 let age = SystemTime::now()
143 .duration_since(modified_time)
144 .map_err(|e| SpsError::Cache(format!("System time error: {e}")))?;
145
146 if age > CACHE_TTL {
147 tracing::debug!("Cache file expired (age: {:?}, TTL: {:?}).", age, CACHE_TTL);
148 return Err(SpsError::Cache(format!(
149 "Cache file expired ({} > {})",
150 humantime::format_duration(age),
151 humantime::format_duration(CACHE_TTL)
152 )));
153 }
154
155 tracing::debug!("Cache file is valid. Loading");
156 let file = fs::File::open(&path)?;
157 let data: T = serde_json::from_reader(file)?;
158 Ok(data)
159}
160
161pub fn clear_cache() -> Result<()> {
163 let path = get_cache_dir()?;
164 tracing::debug!("Clearing cache directory: {:?}", path);
165 if path.exists() {
166 fs::remove_dir_all(&path)?;
167 }
168 Ok(())
169}
170
171pub fn is_cache_valid(filename: &str) -> Result<bool> {
173 let path = get_cache_path(filename)?;
174 if !path.exists() {
175 return Ok(false);
176 }
177 let metadata = fs::metadata(&path)?;
178 let modified_time = metadata.modified()?;
179 let age = SystemTime::now()
180 .duration_since(modified_time)
181 .map_err(|e| SpsError::Cache(format!("System time error: {e}")))?;
182 Ok(age <= CACHE_TTL)
183}