Mountain/Environment/DocumentProvider/
SaveOperations.rs1use std::{path::PathBuf, sync::Arc};
30
31use CommonLibrary::{
32 Effect::ApplicationRunTime::ApplicationRunTime as _,
33 Error::CommonError::CommonError,
34 FileSystem::WriteFileBytes::WriteFileBytes,
35 IPC::SkyEvent::SkyEvent,
36 UserInterface::{DTO::SaveDialogOptionsDTO::SaveDialogOptionsDTO, ShowSaveDialog::ShowSaveDialog},
37};
38use serde_json::json;
39use tauri::{Emitter, Manager};
40use url::Url;
41
42use crate::{
43 ApplicationState::DTO::DocumentStateDTO::DocumentStateDTO,
44 Environment::Utility,
45 RunTime::ApplicationRunTime::ApplicationRunTime,
46 dev_log,
47};
48
49pub(super) async fn save_document(
51 environment:&crate::Environment::MountainEnvironment::MountainEnvironment,
52
53 uri:Url,
54) -> Result<bool, CommonError> {
55 dev_log!("model", "[DocumentProvider] Saving document: {}", uri);
56
57 let (content_bytes, file_path) = {
58 let mut open_documents_guard = environment
59 .ApplicationState
60 .Feature
61 .Documents
62 .OpenDocuments
63 .lock()
64 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?;
65
66 if let Some(document) = open_documents_guard.get_mut(uri.as_str()) {
67 if uri.scheme() != "file" {
69 dev_log!(
70 "model",
71 "[DocumentProvider] Saving non-file URI '{}' to temporary location",
72 uri
73 );
74 }
75
76 document.IsDirty = false;
77
78 (
79 document.GetText().into_bytes(),
80 uri.to_file_path().map_err(|_| {
81 CommonError::InvalidArgument {
82 ArgumentName:"URI".into(),
83 Reason:"Cannot convert file URI to path".into(),
84 }
85 })?,
86 )
87 } else {
88 return Err(CommonError::FileSystemNotFound(uri.to_file_path().unwrap_or_default()));
89 }
90 };
91
92 let runtime = environment.ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
93
94 runtime.Run(WriteFileBytes(file_path, content_bytes, true, true)).await?;
95
96 if let Err(error) = environment
97 .ApplicationHandle
98 .emit(SkyEvent::DocumentsSaved.AsStr(), json!({ "uri": uri.to_string() }))
99 {
100 dev_log!(
101 "model",
102 "error: [DocumentProvider] Failed to emit document saved event: {}",
103 error
104 );
105 }
106
107 crate::Environment::DocumentProvider::Notifications::notify_model_saved(environment, &uri).await;
108
109 Ok(true)
110}
111
112pub(super) async fn save_document_as(
114 environment:&crate::Environment::MountainEnvironment::MountainEnvironment,
115
116 original_uri:Url,
117
118 new_target_uri:Option<Url>,
119) -> Result<Option<Url>, CommonError> {
120 dev_log!("model", "[DocumentProvider] Saving document as: {}", original_uri);
121
122 let runtime = environment.ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
123
124 let new_file_path = match new_target_uri {
125 Some(uri) => uri.to_file_path().ok(),
126
127 None => runtime.Run(ShowSaveDialog(Some(SaveDialogOptionsDTO::default()))).await?,
128 };
129
130 let Some(new_path) = new_file_path else { return Ok(None) };
131
132 let new_uri = Url::from_file_path(&new_path).map_err(|_| {
133 CommonError::InvalidArgument {
134 ArgumentName:"NewPath".into(),
135 Reason:"Could not convert new path to URI".into(),
136 }
137 })?;
138
139 let original_content = {
140 let guard = environment
141 .ApplicationState
142 .Feature
143 .Documents
144 .OpenDocuments
145 .lock()
146 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?;
147
148 guard
149 .get(original_uri.as_str())
150 .map(|doc| doc.GetText())
151 .ok_or_else(|| CommonError::FileSystemNotFound(PathBuf::from(original_uri.path())))?
152 };
153
154 runtime
155 .Run(WriteFileBytes(new_path, original_content.clone().into_bytes(), true, true))
156 .await?;
157
158 let new_document_state = {
159 let mut guard = environment
160 .ApplicationState
161 .Feature
162 .Documents
163 .OpenDocuments
164 .lock()
165 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?;
166
167 let old_document = guard.remove(original_uri.as_str());
168
169 let new_document =
170 DocumentStateDTO::Create(new_uri.clone(), old_document.map(|d| d.LanguageIdentifier), original_content)?;
171
172 let dto = new_document.ToDTO()?;
173
174 guard.insert(new_uri.to_string(), new_document);
175
176 dto
177 };
178
179 crate::Environment::DocumentProvider::Notifications::notify_model_removed(environment, &original_uri).await;
180
181 crate::Environment::DocumentProvider::Notifications::notify_model_added(environment, &new_document_state).await;
182
183 if let Err(error) = environment.ApplicationHandle.emit(
184 SkyEvent::DocumentsRenamed.AsStr(),
185 json!({ "oldUri": original_uri.to_string(), "newUri": new_uri.to_string() }),
186 ) {
187 dev_log!(
188 "model",
189 "error: [DocumentProvider] Failed to emit document renamed event: {}",
190 error
191 );
192 }
193
194 Ok(Some(new_uri))
195}
196
197pub(super) async fn save_all_documents(
199 environment:&crate::Environment::MountainEnvironment::MountainEnvironment,
200
201 include_untitled:bool,
202) -> Result<Vec<bool>, CommonError> {
203 dev_log!(
204 "model",
205 "[DocumentProvider] SaveAllDocuments called (IncludeUntitled: {})",
206 include_untitled
207 );
208
209 let uris_to_save:Vec<Url> = {
210 let open_documents_guard = environment
211 .ApplicationState
212 .Feature
213 .Documents
214 .OpenDocuments
215 .lock()
216 .map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?;
217
218 open_documents_guard
219 .values()
220 .filter(|document| {
221 if !document.IsDirty {
223 return false;
224 }
225
226 if !include_untitled && document.URI.scheme() != "file" {
228 return false;
229 }
230
231 true
232 })
233 .map(|document| document.URI.clone())
234 .collect()
235 };
236
237 let mut results = Vec::with_capacity(uris_to_save.len());
238
239 dev_log!("model", "[DocumentProvider] Saving {} dirty document(s)", uris_to_save.len());
240
241 for uri in uris_to_save {
242 let result = save_document(environment, uri.clone()).await;
243
244 match &result {
245 Ok(_) => {
246 dev_log!("model", "[DocumentProvider] Successfully saved {}", uri);
247 },
248
249 Err(error) => {
250 dev_log!("model", "error: [DocumentProvider] Failed to save {}: {}", uri, error);
251 },
252 }
253
254 results.push(result.is_ok());
255 }
256
257 Ok(results)
258}