Mountain/Environment/
StorageProvider.rs

1// File: Mountain/Source/Environment/StorageProvider.rs
2// Role: Implements the `StorageProvider` trait for the `MountainEnvironment`.
3// Responsibilities:
4//   - Core logic for Memento storage operations.
5//   - Reading from and writing to global and workspace JSON storage files.
6//   - Provides both per-key and high-performance batch operations.
7
8//! # StorageProvider Implementation
9//!
10//! Implements the `StorageProvider` trait for the `MountainEnvironment`. This
11//! provider contains the core logic for Memento storage operations, including
12//! reading from and writing to the appropriate JSON storage files on disk.
13
14#![allow(non_snake_case, non_camel_case_types)]
15
16use std::{collections::HashMap, path::PathBuf};
17
18use Common::{Error::CommonError::CommonError, Storage::StorageProvider::StorageProvider};
19use async_trait::async_trait;
20use log::{error, info, trace};
21use serde_json::Value;
22use tokio::fs;
23
24use super::{MountainEnvironment::MountainEnvironment, Utility};
25
26#[async_trait]
27impl StorageProvider for MountainEnvironment {
28	/// Retrieves a value from either global or workspace storage.
29	async fn GetStorageValue(&self, IsGlobalScope:bool, Key:&str) -> Result<Option<Value>, CommonError> {
30		let ScopeName = if IsGlobalScope { "Global" } else { "WorkSpace" };
31
32		trace!("[StorageProvider] Getting value from {} scope for key: {}", ScopeName, Key);
33
34		let StorageMapMutex = if IsGlobalScope {
35			&self.ApplicationState.GlobalMemento
36		} else {
37			&self.ApplicationState.WorkSpaceMemento
38		};
39
40		let StorageMapGuard = StorageMapMutex
41			.lock()
42			.map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
43
44		Ok(StorageMapGuard.get(Key).cloned())
45	}
46
47	/// Updates or deletes a value in either global or workspace storage.
48	async fn UpdateStorageValue(
49		&self,
50
51		IsGlobalScope:bool,
52
53		Key:String,
54
55		ValueToSet:Option<Value>,
56	) -> Result<(), CommonError> {
57		let ScopeName = if IsGlobalScope { "Global" } else { "WorkSpace" };
58
59		info!("[StorageProvider] Updating value in {} scope for key: {}", ScopeName, Key);
60
61		let (StorageMapMutex, StoragePathOption) = if IsGlobalScope {
62			(
63				self.ApplicationState.GlobalMemento.clone(),
64				Some(self.ApplicationState.GlobalMementoPath.clone()),
65			)
66		} else {
67			(
68				self.ApplicationState.WorkSpaceMemento.clone(),
69				self.ApplicationState
70					.WorkSpaceMementoPath
71					.lock()
72					.map_err(Utility::MapApplicationStateLockErrorToCommonError)?
73					.clone(),
74			)
75		};
76
77		// Perform the in-memory update.
78		let DataToSave = {
79			let mut StorageMapGuard = StorageMapMutex
80				.lock()
81				.map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
82
83			if let Some(Value) = ValueToSet {
84				StorageMapGuard.insert(Key, Value);
85			} else {
86				StorageMapGuard.remove(&Key);
87			}
88
89			StorageMapGuard.clone()
90		};
91
92		if let Some(StoragePath) = StoragePathOption {
93			tokio::spawn(async move {
94				SaveStorageToDisk(StoragePath, DataToSave).await;
95			});
96		}
97
98		Ok(())
99	}
100
101	/// Retrieves the entire storage map for a given scope.
102	async fn GetAllStorage(&self, IsGlobalScope:bool) -> Result<Value, CommonError> {
103		let ScopeName = if IsGlobalScope { "Global" } else { "WorkSpace" };
104
105		trace!("[StorageProvider] Getting all values from {} scope.", ScopeName);
106
107		let StorageMapMutex = if IsGlobalScope {
108			&self.ApplicationState.GlobalMemento
109		} else {
110			&self.ApplicationState.WorkSpaceMemento
111		};
112
113		let StorageMapGuard = StorageMapMutex
114			.lock()
115			.map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
116
117		Ok(serde_json::to_value(&*StorageMapGuard)?)
118	}
119
120	/// Overwrites the entire storage map for a given scope and persists it.
121	async fn SetAllStorage(&self, IsGlobalScope:bool, FullState:Value) -> Result<(), CommonError> {
122		let ScopeName = if IsGlobalScope { "Global" } else { "WorkSpace" };
123
124		info!("[StorageProvider] Setting all values for {} scope.", ScopeName);
125
126		let DeserializedState:HashMap<String, Value> = serde_json::from_value(FullState)?;
127
128		let (StorageMapMutex, StoragePathOption) = if IsGlobalScope {
129			(
130				self.ApplicationState.GlobalMemento.clone(),
131				Some(self.ApplicationState.GlobalMementoPath.clone()),
132			)
133		} else {
134			(
135				self.ApplicationState.WorkSpaceMemento.clone(),
136				self.ApplicationState
137					.WorkSpaceMementoPath
138					.lock()
139					.map_err(Utility::MapApplicationStateLockErrorToCommonError)?
140					.clone(),
141			)
142		};
143
144		// Update in-memory state
145		*StorageMapMutex
146			.lock()
147			.map_err(Utility::MapApplicationStateLockErrorToCommonError)? = DeserializedState.clone();
148
149		// Persist to disk asynchronously
150		if let Some(StoragePath) = StoragePathOption {
151			tokio::spawn(async move {
152				SaveStorageToDisk(StoragePath, DeserializedState).await;
153			});
154		}
155
156		Ok(())
157	}
158}
159
160// --- Internal Helper Functions ---
161
162/// An internal helper function to asynchronously write the storage map to a
163/// file.
164async fn SaveStorageToDisk(Path:PathBuf, Data:HashMap<String, Value>) {
165	trace!("[StorageProvider] Persisting storage to disk: {}", Path.display());
166
167	match serde_json::to_string_pretty(&Data) {
168		Ok(JSONString) => {
169			if let Some(ParentDirectory) = Path.parent() {
170				if let Err(Error) = fs::create_dir_all(ParentDirectory).await {
171					error!(
172						"[StorageProvider] Failed to create parent directory for '{}': {}",
173						Path.display(),
174						Error
175					);
176
177					return;
178				}
179			}
180
181			if let Err(Error) = fs::write(&Path, JSONString).await {
182				error!(
183					"[StorageProvider] Failed to write storage file to '{}': {}",
184					Path.display(),
185					Error
186				);
187			}
188		},
189
190		Err(Error) => {
191			error!(
192				"[StorageProvider] Failed to serialize storage data for '{}': {}",
193				Path.display(),
194				Error
195			);
196		},
197	}
198}