windjammer_runtime/platform/native/
storage.rs1use std::fs;
2use std::path::PathBuf;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5pub type StorageResult<T> = Result<T, String>;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum StorageBackend {
10 Local, Session, Persistent, }
14
15fn get_storage_dir(backend: StorageBackend) -> StorageResult<PathBuf> {
17 let base_dir =
18 dirs::data_local_dir().ok_or_else(|| "Failed to get local data directory".to_string())?;
19
20 let app_dir = base_dir.join("windjammer");
21
22 let storage_dir = match backend {
23 StorageBackend::Local => app_dir.join("storage"),
24 StorageBackend::Session => std::env::temp_dir().join("windjammer_session"),
25 StorageBackend::Persistent => app_dir.join("persistent"),
26 };
27
28 fs::create_dir_all(&storage_dir)
30 .map_err(|e| format!("Failed to create storage directory: {}", e))?;
31
32 Ok(storage_dir)
33}
34
35fn get_key_path(key: &str, backend: StorageBackend) -> StorageResult<PathBuf> {
37 let storage_dir = get_storage_dir(backend)?;
38 let safe_key = key.replace(['/', '\\'], "_").replace("..", "_");
40 Ok(storage_dir.join(safe_key))
41}
42
43pub fn set(key: String, value: String) -> StorageResult<()> {
45 set_with_backend(key, value, StorageBackend::Local)
46}
47
48pub fn get(key: String) -> StorageResult<Option<String>> {
50 get_with_backend(key, StorageBackend::Local)
51}
52
53pub fn remove(key: String) -> StorageResult<()> {
55 remove_with_backend(key, StorageBackend::Local)
56}
57
58pub fn clear() -> StorageResult<()> {
60 clear_with_backend(StorageBackend::Local)
61}
62
63pub fn keys() -> StorageResult<Vec<String>> {
65 keys_with_backend(StorageBackend::Local)
66}
67
68pub fn has(key: String) -> bool {
70 get(key).unwrap_or(None).is_some()
71}
72
73pub fn set_with_backend(key: String, value: String, backend: StorageBackend) -> StorageResult<()> {
77 let path = get_key_path(&key, backend)?;
78 fs::write(&path, value).map_err(|e| format!("Failed to write storage file: {}", e))?;
79 Ok(())
80}
81
82pub fn get_with_backend(key: String, backend: StorageBackend) -> StorageResult<Option<String>> {
84 let path = get_key_path(&key, backend)?;
85 if !path.exists() {
86 return Ok(None);
87 }
88 let value =
89 fs::read_to_string(&path).map_err(|e| format!("Failed to read storage file: {}", e))?;
90 Ok(Some(value))
91}
92
93pub fn remove_with_backend(key: String, backend: StorageBackend) -> StorageResult<()> {
95 let path = get_key_path(&key, backend)?;
96 if path.exists() {
97 fs::remove_file(&path).map_err(|e| format!("Failed to remove storage file: {}", e))?;
98 }
99 Ok(())
100}
101
102pub fn clear_with_backend(backend: StorageBackend) -> StorageResult<()> {
104 let storage_dir = get_storage_dir(backend)?;
105 if storage_dir.exists() {
106 fs::remove_dir_all(&storage_dir)
107 .map_err(|e| format!("Failed to clear storage directory: {}", e))?;
108 fs::create_dir_all(&storage_dir)
109 .map_err(|e| format!("Failed to recreate storage directory: {}", e))?;
110 }
111 Ok(())
112}
113
114pub fn keys_with_backend(backend: StorageBackend) -> StorageResult<Vec<String>> {
116 let storage_dir = get_storage_dir(backend)?;
117 let mut keys = Vec::new();
118
119 if storage_dir.exists() {
120 let entries = fs::read_dir(&storage_dir)
121 .map_err(|e| format!("Failed to read storage directory: {}", e))?;
122
123 for entry in entries {
124 let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
125 if let Some(name) = entry.file_name().to_str() {
126 keys.push(name.to_string());
127 }
128 }
129 }
130
131 Ok(keys)
132}
133
134pub fn set_json<T: serde::Serialize>(key: String, value: T) -> StorageResult<()> {
138 let json =
139 serde_json::to_string(&value).map_err(|e| format!("Failed to serialize to JSON: {}", e))?;
140 set(key, json)
141}
142
143pub fn get_json<T: serde::de::DeserializeOwned>(key: String) -> StorageResult<Option<T>> {
145 match get(key)? {
146 Some(json) => {
147 let value = serde_json::from_str(&json)
148 .map_err(|e| format!("Failed to deserialize from JSON: {}", e))?;
149 Ok(Some(value))
150 }
151 None => Ok(None),
152 }
153}
154
155pub fn set_bytes(key: String, data: Vec<u8>) -> StorageResult<()> {
159 let path = get_key_path(&key, StorageBackend::Local)?;
160 fs::write(&path, data).map_err(|e| format!("Failed to write binary data: {}", e))?;
161 Ok(())
162}
163
164pub fn get_bytes(key: String) -> StorageResult<Option<Vec<u8>>> {
166 let path = get_key_path(&key, StorageBackend::Local)?;
167 if !path.exists() {
168 return Ok(None);
169 }
170 let data = fs::read(&path).map_err(|e| format!("Failed to read binary data: {}", e))?;
171 Ok(Some(data))
172}
173
174#[derive(serde::Serialize, serde::Deserialize)]
178struct TtlMetadata {
179 value: String,
180 expires_at: u64, }
182
183pub fn set_with_ttl(key: String, value: String, ttl: i32) -> StorageResult<()> {
185 let now = SystemTime::now()
186 .duration_since(UNIX_EPOCH)
187 .map_err(|e| format!("Failed to get current time: {}", e))?
188 .as_secs();
189
190 let expires_at = now + ttl as u64;
191
192 let metadata = TtlMetadata { value, expires_at };
193
194 set_json(format!("{}_ttl", key), metadata)
195}
196
197pub fn get_with_ttl(key: String) -> StorageResult<Option<String>> {
199 let metadata: Option<TtlMetadata> = get_json(format!("{}_ttl", key))?;
200
201 match metadata {
202 Some(meta) => {
203 let now = SystemTime::now()
204 .duration_since(UNIX_EPOCH)
205 .map_err(|e| format!("Failed to get current time: {}", e))?
206 .as_secs();
207
208 if now < meta.expires_at {
209 Ok(Some(meta.value))
210 } else {
211 remove(format!("{}_ttl", key))?;
213 Ok(None)
214 }
215 }
216 None => Ok(None),
217 }
218}