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