Skip to main content

windjammer_runtime/platform/native/
storage.rs

1use std::fs;
2use std::path::PathBuf;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5pub type StorageResult<T> = Result<T, String>;
6
7/// Storage backend types
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum StorageBackend {
10    Local,      // Local file storage
11    Session,    // Temporary file storage
12    Persistent, // Database storage (SQLite)
13}
14
15/// Get the storage directory for a given backend
16fn 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    // Create directory if it doesn't exist
29    fs::create_dir_all(&storage_dir)
30        .map_err(|e| format!("Failed to create storage directory: {}", e))?;
31
32    Ok(storage_dir)
33}
34
35/// Get the file path for a key
36fn get_key_path(key: &str, backend: StorageBackend) -> StorageResult<PathBuf> {
37    let storage_dir = get_storage_dir(backend)?;
38    // Sanitize key to prevent directory traversal
39    let safe_key = key.replace(['/', '\\'], "_").replace("..", "_");
40    Ok(storage_dir.join(safe_key))
41}
42
43/// Store a key-value pair
44pub fn set(key: String, value: String) -> StorageResult<()> {
45    set_with_backend(key, value, StorageBackend::Local)
46}
47
48/// Get a value by key
49pub fn get(key: String) -> StorageResult<Option<String>> {
50    get_with_backend(key, StorageBackend::Local)
51}
52
53/// Remove a key
54pub fn remove(key: String) -> StorageResult<()> {
55    remove_with_backend(key, StorageBackend::Local)
56}
57
58/// Clear all storage
59pub fn clear() -> StorageResult<()> {
60    clear_with_backend(StorageBackend::Local)
61}
62
63/// List all keys
64pub fn keys() -> StorageResult<Vec<String>> {
65    keys_with_backend(StorageBackend::Local)
66}
67
68/// Check if a key exists
69pub fn has(key: String) -> bool {
70    get(key).unwrap_or(None).is_some()
71}
72
73// Backend-specific functions
74
75/// Store with specific backend
76pub 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
82/// Get with specific backend
83pub 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
93/// Remove with specific backend
94pub 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
102/// Clear with specific backend
103pub 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
114/// Keys with specific backend
115pub 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
134// Structured storage (JSON)
135
136/// Store a JSON-serializable value
137pub 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
143/// Get a JSON-serializable value
144pub 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
155// Binary storage
156
157/// Store binary data
158pub 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
164/// Get binary data
165pub 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// Cache management with TTL
175
176/// Metadata for TTL storage
177#[derive(serde::Serialize, serde::Deserialize)]
178struct TtlMetadata {
179    value: String,
180    expires_at: u64, // Unix timestamp
181}
182
183/// Set with expiration (seconds)
184pub 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
197/// Get with automatic expiration check
198pub 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                // Expired, remove it
212                remove(format!("{}_ttl", key))?;
213                Ok(None)
214            }
215        }
216        None => Ok(None),
217    }
218}