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