1use crate::error::WalletError;
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::marker::PhantomData;
5use std::path::{Path, PathBuf};
6
7pub struct FileCache<T>
9where
10 T: Serialize + for<'de> Deserialize<'de>,
11{
12 cache_dir: PathBuf,
13 _phantom: PhantomData<T>,
14}
15
16impl<T> FileCache<T>
17where
18 T: Serialize + for<'de> Deserialize<'de>,
19{
20 pub fn new(relative_file_path: &str, base_dir: Option<&Path>) -> Result<Self, WalletError> {
22 let base_path = match base_dir {
23 Some(dir) => dir.to_path_buf(),
24 None => dirs::home_dir()
25 .ok_or_else(|| {
26 WalletError::FileSystemError("Could not find home directory".to_string())
27 })?
28 .join(".dig"),
29 };
30
31 let cache_dir = base_path.join(relative_file_path);
32
33 let cache = Self {
34 cache_dir,
35 _phantom: PhantomData,
36 };
37 cache.ensure_directory_exists()?;
38
39 Ok(cache)
40 }
41
42 fn ensure_directory_exists(&self) -> Result<(), WalletError> {
44 if !self.cache_dir.exists() {
45 fs::create_dir_all(&self.cache_dir).map_err(|e| {
46 WalletError::FileSystemError(format!("Failed to create cache directory: {}", e))
47 })?;
48 }
49 Ok(())
50 }
51
52 fn get_cache_file_path(&self, key: &str) -> PathBuf {
54 self.cache_dir.join(format!("{}.json", key))
55 }
56
57 pub fn get(&self, key: &str) -> Result<Option<T>, WalletError> {
59 let cache_file_path = self.get_cache_file_path(key);
60
61 if !cache_file_path.exists() {
62 return Ok(None);
63 }
64
65 let raw_data = fs::read_to_string(&cache_file_path).map_err(|e| {
66 WalletError::FileSystemError(format!("Failed to read cache file: {}", e))
67 })?;
68
69 let data: T = serde_json::from_str(&raw_data).map_err(|e| {
70 WalletError::SerializationError(format!("Failed to deserialize cache data: {}", e))
71 })?;
72
73 Ok(Some(data))
74 }
75
76 pub fn set(&self, key: &str, data: &T) -> Result<(), WalletError> {
78 let cache_file_path = self.get_cache_file_path(key);
79
80 let serialized_data = serde_json::to_string_pretty(data).map_err(|e| {
81 WalletError::SerializationError(format!("Failed to serialize cache data: {}", e))
82 })?;
83
84 fs::write(&cache_file_path, serialized_data).map_err(|e| {
85 WalletError::FileSystemError(format!("Failed to write cache file: {}", e))
86 })?;
87
88 Ok(())
89 }
90
91 pub fn delete(&self, key: &str) -> Result<(), WalletError> {
93 let cache_file_path = self.get_cache_file_path(key);
94
95 if cache_file_path.exists() {
96 fs::remove_file(&cache_file_path).map_err(|e| {
97 WalletError::FileSystemError(format!("Failed to delete cache file: {}", e))
98 })?;
99 }
100
101 Ok(())
102 }
103
104 pub fn get_cached_keys(&self) -> Result<Vec<String>, WalletError> {
106 if !self.cache_dir.exists() {
107 return Ok(vec![]);
108 }
109
110 let entries = fs::read_dir(&self.cache_dir).map_err(|e| {
111 WalletError::FileSystemError(format!("Failed to read cache directory: {}", e))
112 })?;
113
114 let mut keys = Vec::new();
115
116 for entry in entries {
117 let entry = entry.map_err(|e| {
118 WalletError::FileSystemError(format!("Failed to read directory entry: {}", e))
119 })?;
120
121 if let Some(file_name) = entry.file_name().to_str() {
122 if file_name.ends_with(".json") {
123 let key = file_name.strip_suffix(".json").unwrap_or(file_name);
124 keys.push(key.to_string());
125 }
126 }
127 }
128
129 Ok(keys)
130 }
131
132 pub fn clear(&self) -> Result<(), WalletError> {
134 let keys = self.get_cached_keys()?;
135
136 for key in keys {
137 self.delete(&key)?;
138 }
139
140 Ok(())
141 }
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct ReservedCoinCache {
146 pub coin_id: String,
147 pub expiry: u64,
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use serde::{Deserialize, Serialize};
154 use tempfile::TempDir;
155
156 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
157 struct TestData {
158 value: String,
159 number: i32,
160 }
161
162 #[test]
163 fn test_file_cache_operations() {
164 let temp_dir = TempDir::new().unwrap();
165 let cache = FileCache::<TestData>::new("test_cache", Some(temp_dir.path())).unwrap();
166
167 let test_data = TestData {
168 value: "test".to_string(),
169 number: 42,
170 };
171
172 cache.set("test_key", &test_data).unwrap();
174 let retrieved = cache.get("test_key").unwrap().unwrap();
175 assert_eq!(retrieved, test_data);
176
177 let non_existent = cache.get("non_existent").unwrap();
179 assert!(non_existent.is_none());
180
181 let keys = cache.get_cached_keys().unwrap();
183 assert_eq!(keys, vec!["test_key"]);
184
185 cache.delete("test_key").unwrap();
187 let deleted = cache.get("test_key").unwrap();
188 assert!(deleted.is_none());
189 }
190}