DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/Environment/
StorageProvider.rs1use std::{
39 collections::HashMap,
40 path::PathBuf,
41 sync::{
42 Arc,
43 Mutex,
44 OnceLock,
45 atomic::{AtomicBool, Ordering},
46 },
47};
48
49use CommonLibrary::{Error::CommonError::CommonError, Storage::StorageProvider::StorageProvider};
50use async_trait::async_trait;
51use serde_json::Value;
52use tokio::fs;
53
54use super::{MountainEnvironment::MountainEnvironment, Utility};
55use crate::dev_log;
56
57struct StorageWriteDebouncer {
61 Pending:Mutex<Option<(PathBuf, HashMap<String, Value>)>>,
62
63 FlushScheduled:AtomicBool,
64}
65
66impl StorageWriteDebouncer {
67 fn new() -> Arc<Self> { Arc::new(Self { Pending:Mutex::new(None), FlushScheduled:AtomicBool::new(false) }) }
68
69 fn Queue(&self, Path:PathBuf, Data:HashMap<String, Value>, Debouncer:Arc<Self>) {
70 if let Ok(mut Guard) = self.Pending.lock() {
71 *Guard = Some((Path, Data));
72 }
73
74 if !self.FlushScheduled.swap(true, Ordering::AcqRel) {
75 tokio::spawn(async move {
76 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
77
78 let Item = {
79 let mut Guard = Debouncer.Pending.lock().unwrap();
80
81 Debouncer.FlushScheduled.store(false, Ordering::Release);
82
83 Guard.take()
84 };
85
86 if let Some((StoragePath, StorageData)) = Item {
87 SaveStorageToDisk(StoragePath, StorageData).await;
88 }
89 });
90 }
91 }
92}
93
94static GLOBAL_DEBOUNCER:OnceLock<Arc<StorageWriteDebouncer>> = OnceLock::new();
95
96static WORKSPACE_DEBOUNCER:OnceLock<Arc<StorageWriteDebouncer>> = OnceLock::new();
97
98fn GetGlobalDebouncer() -> Arc<StorageWriteDebouncer> {
99 GLOBAL_DEBOUNCER.get_or_init(StorageWriteDebouncer::new).clone()
100}
101
102fn GetWorkspaceDebouncer() -> Arc<StorageWriteDebouncer> {
103 WORKSPACE_DEBOUNCER.get_or_init(StorageWriteDebouncer::new).clone()
104}
105
106#[async_trait]
111impl StorageProvider for MountainEnvironment {
112 async fn GetStorageValue(&self, IsGlobalScope:bool, Key:&str) -> Result<Option<Value>, CommonError> {
115 let ScopeName = if IsGlobalScope { "Global" } else { "Workspace" };
116
117 dev_log!(
118 "storage",
119 "[StorageProvider] Getting value from {} scope for key: {}",
120 ScopeName,
121 Key
122 );
123
124 if Key.is_empty() {
126 return Ok(None);
127 }
128
129 if Key.len() > 1024 {
130 return Err(CommonError::InvalidArgument {
131 ArgumentName:"Key".into(),
132 Reason:"Key length exceeds maximum allowed length of 1024 characters".into(),
133 });
134 }
135
136 let StorageMapMutex = if IsGlobalScope {
137 &self.ApplicationState.Configuration.MementoGlobalStorage
138 } else {
139 &self.ApplicationState.Configuration.MementoWorkspaceStorage
140 };
141
142 let StorageMapGuard = StorageMapMutex
143 .lock()
144 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?;
145
146 Ok(StorageMapGuard.get(Key).cloned())
147 }
148
149 async fn UpdateStorageValue(
153 &self,
154
155 IsGlobalScope:bool,
156
157 Key:String,
158
159 ValueToSet:Option<Value>,
160 ) -> Result<(), CommonError> {
161 let ScopeName = if IsGlobalScope { "Global" } else { "Workspace" };
162
163 if crate::IPC::DevLog::IsShort::Fn() {
169 crate::dev_log!("storage-verbose", "update {} {}", ScopeName, Key);
170 } else {
171 dev_log!(
172 "storage-verbose",
173 "[StorageProvider] Updating value in {} scope for key: {}",
174 ScopeName,
175 Key
176 );
177 }
178
179 if Key.is_empty() {
181 return Err(CommonError::InvalidArgument {
182 ArgumentName:"Key".into(),
183 Reason:"Key cannot be empty".into(),
184 });
185 }
186
187 if Key.len() > 1024 {
188 return Err(CommonError::InvalidArgument {
189 ArgumentName:"Key".into(),
190 Reason:"Key length exceeds maximum allowed length of 1024 characters".into(),
191 });
192 }
193
194 if let Some(ref value) = ValueToSet {
196 if let Ok(json_string) = serde_json::to_string(value) {
197 if json_string.len() > 10 * 1024 * 1024 {
198 return Err(CommonError::InvalidArgument {
200 ArgumentName:"ValueToSet".into(),
201 Reason:"Value size exceeds maximum allowed size of 10MB".into(),
202 });
203 }
204 }
205 }
206
207 let (StorageMapMutex, StoragePathOption) = if IsGlobalScope {
208 (
209 self.ApplicationState.Configuration.MementoGlobalStorage.clone(),
210 Some(
211 self.ApplicationState
212 .GlobalMementoPath
213 .lock()
214 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?
215 .clone(),
216 ),
217 )
218 } else {
219 (
220 self.ApplicationState.Configuration.MementoWorkspaceStorage.clone(),
221 self.ApplicationState
222 .WorkspaceMementoPath
223 .lock()
224 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?
225 .clone(),
226 )
227 };
228
229 let DataToSave = {
231 let mut StorageMapGuard = StorageMapMutex
232 .lock()
233 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?;
234
235 if let Some(Value) = ValueToSet {
236 StorageMapGuard.insert(Key, Value);
237 } else {
238 StorageMapGuard.remove(&Key);
239 }
240
241 StorageMapGuard.clone()
242 };
243
244 if let Some(StoragePath) = StoragePathOption {
245 let Debouncer = if IsGlobalScope { GetGlobalDebouncer() } else { GetWorkspaceDebouncer() };
248
249 Debouncer.Queue(StoragePath, DataToSave, Debouncer.clone());
250 }
251
252 Ok(())
253 }
254
255 async fn GetAllStorage(&self, IsGlobalScope:bool) -> Result<Value, CommonError> {
257 let ScopeName = if IsGlobalScope { "Global" } else { "Workspace" };
258
259 dev_log!(
260 "storage-verbose",
261 "[StorageProvider] Getting all values from {} scope.",
262 ScopeName
263 );
264
265 let StorageMapMutex = if IsGlobalScope {
266 &self.ApplicationState.Configuration.MementoGlobalStorage
267 } else {
268 &self.ApplicationState.Configuration.MementoWorkspaceStorage
269 };
270
271 let StorageMapGuard = StorageMapMutex
272 .lock()
273 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?;
274
275 Ok(serde_json::to_value(&*StorageMapGuard)?)
276 }
277
278 async fn SetAllStorage(&self, IsGlobalScope:bool, FullState:Value) -> Result<(), CommonError> {
280 let ScopeName = if IsGlobalScope { "Global" } else { "Workspace" };
281
282 dev_log!(
283 "storage-verbose",
284 "[StorageProvider] Setting all values for {} scope.",
285 ScopeName
286 );
287
288 let DeserializedState:HashMap<String, Value> = serde_json::from_value(FullState)?;
289
290 let (StorageMapMutex, StoragePathOption) = if IsGlobalScope {
291 (
292 self.ApplicationState.Configuration.MementoGlobalStorage.clone(),
293 Some(
294 self.ApplicationState
295 .GlobalMementoPath
296 .lock()
297 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?
298 .clone(),
299 ),
300 )
301 } else {
302 (
303 self.ApplicationState.Configuration.MementoWorkspaceStorage.clone(),
304 self.ApplicationState
305 .WorkspaceMementoPath
306 .lock()
307 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?
308 .clone(),
309 )
310 };
311
312 *StorageMapMutex
314 .lock()
315 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)? = DeserializedState.clone();
316
317 if let Some(StoragePath) = StoragePathOption {
319 let Debouncer = GetGlobalDebouncer();
320
321 Debouncer.Queue(StoragePath, DeserializedState, Debouncer.clone());
322 }
323
324 Ok(())
325 }
326}
327
328async fn SaveStorageToDisk(Path:PathBuf, Data:HashMap<String, Value>) {
333 dev_log!(
337 "storage-verbose",
338 "[StorageProvider] Persisting storage to disk: {}",
339 Path.display()
340 );
341
342 match serde_json::to_string_pretty(&Data) {
343 Ok(JSONString) => {
344 if let Some(ParentDirectory) = Path.parent() {
345 if let Err(Error) = fs::create_dir_all(ParentDirectory).await {
346 dev_log!(
347 "storage",
348 "error: [StorageProvider] Failed to create parent directory for '{}': {}",
349 Path.display(),
350 Error
351 );
352
353 return;
354 }
355 }
356
357 if let Err(Error) = fs::write(&Path, JSONString).await {
358 dev_log!(
359 "storage",
360 "error: [StorageProvider] Failed to write storage file to '{}': {}",
361 Path.display(),
362 Error
363 );
364 }
365 },
366
367 Err(Error) => {
368 dev_log!(
369 "storage",
370 "error: [StorageProvider] Failed to serialize storage data for '{}': {}",
371 Path.display(),
372 Error
373 );
374 },
375 }
376}