Mountain/Environment/ConfigurationProvider/
UpdateValue.rs1use std::{path::PathBuf, sync::Arc};
23
24use CommonLibrary::{
25 Configuration::DTO::{
26 ConfigurationOverridesDTO::ConfigurationOverridesDTO,
27 ConfigurationTarget::ConfigurationTarget,
28 },
29 Effect::ApplicationRunTime::ApplicationRunTime as _,
30 Error::CommonError::CommonError,
31 FileSystem::{ReadFile::ReadFile, WriteFileBytes::WriteFileBytes},
32 IPC::SkyEvent::SkyEvent,
33};
34use serde_json::{Map, Value};
35use tauri::Manager;
36
37use crate::{Environment::Utility, IPC::SkyEmit::LogSkyEmit, RunTime::ApplicationRunTime::ApplicationRunTime, dev_log};
38
39pub(super) async fn update_configuration_value(
41 environment:&crate::Environment::MountainEnvironment::MountainEnvironment,
42
43 key:String,
44
45 value:Value,
46
47 target:ConfigurationTarget,
48
49 overrides:ConfigurationOverridesDTO,
50
51 _scope_to_language:Option<bool>,
52) -> Result<(), CommonError> {
53 dev_log!(
54 "config",
55 "[ConfigurationProvider] Updating key '{}' in target {:?}",
56 key,
57 target
58 );
59
60 let runtime = environment.ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
61
62 let config_path:PathBuf = match target {
63 ConfigurationTarget::UserLocal | ConfigurationTarget::User => {
68 environment
69 .ApplicationHandle
70 .path()
71 .app_config_dir()
72 .map(|p| p.join("settings.json"))
73 .map_err(|error| {
74 CommonError::ConfigurationLoad {
75 Description:format!("Could not resolve user config path: {}", error),
76 }
77 })?
78 },
79
80 ConfigurationTarget::Workspace => {
81 environment
82 .ApplicationState
83 .Workspace
84 .WorkspaceConfigurationPath
85 .lock()
86 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?
87 .clone()
88 .ok_or_else(|| {
89 CommonError::ConfigurationLoad { Description:"No workspace configuration path set".into() }
90 })?
91 },
92
93 ConfigurationTarget::WorkspaceFolder => {
99 let FoldersGuard = environment
100 .ApplicationState
101 .Workspace
102 .WorkspaceFolders
103 .lock()
104 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?;
105
106 let First = FoldersGuard.first().ok_or_else(|| {
107 CommonError::ConfigurationLoad {
108 Description:"No workspace folders open for WorkspaceFolder target".into(),
109 }
110 })?;
111
112 let FolderPath = First.URI.to_file_path().map_err(|_| {
113 CommonError::ConfigurationLoad {
114 Description:format!("Workspace folder URI is not a local path: {}", First.URI),
115 }
116 })?;
117
118 FolderPath.join(".vscode").join("settings.json")
119 },
120
121 ConfigurationTarget::Memory => {
127 environment.ApplicationState.Configuration.SetGlobalValue(&key, value.clone());
128
129 dev_log!(
130 "config",
131 "[ConfigurationProvider] Memory target: stored in-memory value for '{}'",
132 key
133 );
134
135 return Ok(());
136 },
137
138 ConfigurationTarget::Default | ConfigurationTarget::Policy => {
140 return Err(CommonError::InvalidArgument {
141 ArgumentName:"target".into(),
142 Reason:format!("Configuration target {:?} is read-only", target),
143 });
144 },
145 };
146
147 let bytes = runtime.Run(ReadFile(config_path.clone())).await.unwrap_or_default();
149
150 let mut current_config:Value = serde_json::from_slice(&bytes).unwrap_or_else(|_| Value::Object(Map::new()));
151
152 if let Value::Object(ref mut RootMap) = current_config {
153 if let Some(LangId) = overrides.OverrideIdentifier.as_deref().filter(|S| !S.is_empty()) {
154 let ScopeKey = format!("[{}]", LangId);
160
161 let LangScope = RootMap.entry(ScopeKey.clone()).or_insert_with(|| Value::Object(Map::new()));
162
163 if let Value::Object(LangMap) = LangScope {
164 if value.is_null() {
165 LangMap.remove(&key);
166
167 if LangMap.is_empty() {
168 RootMap.remove(&ScopeKey);
169 }
170
171 dev_log!("config", "[ConfigurationProvider] Removed '[{}]' key '{}'", LangId, key);
172 } else {
173 LangMap.insert(key.clone(), value.clone());
174
175 dev_log!("config", "[ConfigurationProvider] Updated '[{}]' key '{}'", LangId, key);
176 }
177 }
178 } else {
179 if value.is_null() {
181 RootMap.remove(&key);
182
183 dev_log!("config", "[ConfigurationProvider] Removed configuration key '{}'", key);
184 } else {
185 RootMap.insert(key.clone(), value.clone());
186
187 dev_log!("config", "[ConfigurationProvider] Updated configuration key '{}'", key);
188 }
189 }
190 }
191
192 let content_bytes = serde_json::to_vec_pretty(¤t_config)?;
193
194 runtime
195 .Run(WriteFileBytes(config_path.clone(), content_bytes, true, true))
196 .await?;
197
198 crate::Environment::ConfigurationProvider::Loading::ClearSettingsFileCache();
203
204 crate::Environment::ConfigurationProvider::Loading::Fn(environment).await?;
206
207 let EmitPayload = serde_json::json!({ "keys": [key] });
211
212 if let Err(Error) = LogSkyEmit(
213 &environment.ApplicationHandle,
214 SkyEvent::ConfigurationChanged.AsStr(),
215 EmitPayload,
216 ) {
217 dev_log!(
218 "config",
219 "warn: [ConfigurationProvider] sky://configuration/changed emit failed: {}",
220 Error
221 );
222 }
223
224 let NotifyKey = key.clone();
231
232 tokio::spawn(async move {
233 if let Err(Error) = crate::Vine::Client::SendNotification::Fn(
234 "cocoon-main".to_string(),
235 "configuration.change".to_string(),
236 serde_json::json!({ "keys": [NotifyKey] }),
237 )
238 .await
239 {
240 crate::dev_log!(
241 "config",
242 "warn: [ConfigurationProvider] configuration.change Cocoon notify failed: {:?}",
243 Error
244 );
245 }
246 });
247
248 Ok(())
249}