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