Mountain/ApplicationState/State/WorkspaceState/
WorkspaceDelta.rs1use CommonLibrary::IPC::SkyEvent::SkyEvent;
20use serde_json::json;
21
22use crate::{
23 ApplicationState::DTO::WorkspaceFolderStateDTO::WorkspaceFolderStateDTO,
24 IPC::SkyEmit::LogSkyEmit,
25 Vine::Client,
26 dev_log,
27};
28
29fn FolderToWire(Folder:&WorkspaceFolderStateDTO) -> serde_json::Value {
34 json!({
35 "uri": Folder.URI.to_string(),
36 "name": Folder.GetDisplayName(),
37 "index": Folder.Index,
38 })
39}
40
41pub async fn DispatchDeltaWorkspaceFolders(Added:Vec<WorkspaceFolderStateDTO>, Removed:Vec<WorkspaceFolderStateDTO>) {
49 if Added.is_empty() && Removed.is_empty() {
50 return;
51 }
52
53 let AddedWire:Vec<serde_json::Value> = Added.iter().map(FolderToWire).collect();
54
55 let RemovedWire:Vec<serde_json::Value> = Removed.iter().map(FolderToWire).collect();
56
57 dev_log!(
58 "workspaces",
59 "[LandFix:WsDelta] $deltaWorkspaceFolders +{} -{} (first added={})",
60 AddedWire.len(),
61 RemovedWire.len(),
62 Added.first().map(|F| F.URI.as_str()).unwrap_or("<none>")
63 );
64
65 let Payload = json!({
66 "added": AddedWire,
67 "removed": RemovedWire,
68 });
69
70 if let Err(Error) =
71 Client::SendNotification::Fn("cocoon-main".to_string(), "$deltaWorkspaceFolders".to_string(), Payload).await
72 {
73 dev_log!(
74 "workspaces",
75 "warn: [LandFix:WsDelta] $deltaWorkspaceFolders notification failed: {}",
76 Error
77 );
78 }
79}
80
81pub fn UpdateWorkspaceFoldersAndNotify(
88 State:&crate::ApplicationState::State::WorkspaceState::WorkspaceState::State,
89
90 Folders:Vec<WorkspaceFolderStateDTO>,
91) {
92 let (Added, Removed) = State.SetWorkspaceFoldersReturnDelta(Folders);
93
94 if Added.is_empty() && Removed.is_empty() {
95 return;
96 }
97
98 if let Ok(Handle) = tokio::runtime::Handle::try_current() {
99 Handle.spawn(async move {
100 DispatchDeltaWorkspaceFolders(Added, Removed).await;
101 });
102 } else {
103 dev_log!(
104 "workspaces",
105 "warn: [LandFix:WsDelta] No tokio runtime available - delta dropped ({} added, {} removed)",
106 Added.len(),
107 Removed.len()
108 );
109 }
110}
111
112pub fn UpdateWorkspaceFoldersAndBroadcast<R:tauri::Runtime>(
117 ApplicationHandle:&tauri::AppHandle<R>,
118
119 State:&crate::ApplicationState::State::WorkspaceState::WorkspaceState::State,
120
121 Folders:Vec<WorkspaceFolderStateDTO>,
122) {
123 let (Added, Removed) = State.SetWorkspaceFoldersReturnDelta(Folders);
128
129 if Added.is_empty() && Removed.is_empty() {
130 return;
131 }
132
133 let AddedWire:Vec<serde_json::Value> = Added.iter().map(FolderToWire).collect();
134
135 let RemovedWire:Vec<serde_json::Value> = Removed.iter().map(FolderToWire).collect();
136
137 let BroadcastPayload = serde_json::json!({
138 "added": AddedWire.clone(),
139 "removed": RemovedWire.clone(),
140 "folders": State
141 .GetWorkspaceFolders()
142 .iter()
143 .map(FolderToWire)
144 .collect::<Vec<_>>(),
145 });
146
147 if let Err(Error) = LogSkyEmit(ApplicationHandle, SkyEvent::WorkspacesChanged.AsStr(), BroadcastPayload) {
148 dev_log!(
149 "workspaces",
150 "warn: [LandFix:WsDelta] sky://workspaces/changed emit failed: {}",
151 Error
152 );
153 }
154
155 PersistRecentlyOpened(&Added);
159
160 if let Ok(Handle) = tokio::runtime::Handle::try_current() {
161 Handle.spawn(async move {
162 DispatchDeltaWorkspaceFolders(Added, Removed).await;
163 });
164 }
165}
166
167fn PersistRecentlyOpened(Added:&[WorkspaceFolderStateDTO]) {
173 if Added.is_empty() {
174 return;
175 }
176
177 let Path = crate::IPC::WindServiceHandlers::Utilities::FiddeeRoot::Fn()
178 .join("workspaces")
179 .join("RecentlyOpened.json");
180
181 let mut Current:serde_json::Map<String, serde_json::Value> = std::fs::read_to_string(&Path)
182 .ok()
183 .and_then(|Contents| serde_json::from_str::<serde_json::Value>(&Contents).ok())
184 .and_then(|V| V.as_object().cloned())
185 .unwrap_or_default();
186
187 let mut Workspaces = Current
188 .get("workspaces")
189 .and_then(|V| V.as_array())
190 .cloned()
191 .unwrap_or_default();
192
193 for Folder in Added {
194 let Uri = Folder.URI.to_string();
195
196 Workspaces.retain(|Entry| Entry.get("uri").and_then(|V| V.as_str()).unwrap_or("") != Uri);
197
198 Workspaces.insert(
199 0,
200 serde_json::json!({
201 "uri": Uri,
202 "label": Folder.GetDisplayName(),
203 }),
204 );
205 }
206
207 Workspaces.truncate(50);
208
209 Current.insert("workspaces".into(), serde_json::Value::Array(Workspaces));
210
211 if !Current.contains_key("files") {
212 Current.insert("files".into(), serde_json::json!([]));
213 }
214
215 if let Some(Parent) = Path.parent() {
216 let _ = std::fs::create_dir_all(Parent);
217 }
218
219 if let Ok(Serialised) = serde_json::to_vec_pretty(&serde_json::Value::Object(Current)) {
220 let _ = std::fs::write(&Path, Serialised);
221 }
222}