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 let RemovedWire:Vec<serde_json::Value> = Removed.iter().map(FolderToWire).collect();
55
56 dev_log!(
57 "workspaces",
58 "[LandFix:WsDelta] $deltaWorkspaceFolders +{} -{} (first added={})",
59 AddedWire.len(),
60 RemovedWire.len(),
61 Added.first().map(|F| F.URI.as_str()).unwrap_or("<none>")
62 );
63
64 let Payload = json!({
65 "added": AddedWire,
66 "removed": RemovedWire,
67 });
68
69 if let Err(Error) =
70 Client::SendNotification::Fn("cocoon-main".to_string(), "$deltaWorkspaceFolders".to_string(), Payload).await
71 {
72 dev_log!(
73 "workspaces",
74 "warn: [LandFix:WsDelta] $deltaWorkspaceFolders notification failed: {}",
75 Error
76 );
77 }
78}
79
80pub fn UpdateWorkspaceFoldersAndNotify(
87 State:&crate::ApplicationState::State::WorkspaceState::WorkspaceState::State,
88 Folders:Vec<WorkspaceFolderStateDTO>,
89) {
90 let (Added, Removed) = State.SetWorkspaceFoldersReturnDelta(Folders);
91 if Added.is_empty() && Removed.is_empty() {
92 return;
93 }
94 if let Ok(Handle) = tokio::runtime::Handle::try_current() {
95 Handle.spawn(async move {
96 DispatchDeltaWorkspaceFolders(Added, Removed).await;
97 });
98 } else {
99 dev_log!(
100 "workspaces",
101 "warn: [LandFix:WsDelta] No tokio runtime available - delta dropped ({} added, {} removed)",
102 Added.len(),
103 Removed.len()
104 );
105 }
106}
107
108pub fn UpdateWorkspaceFoldersAndBroadcast<R:tauri::Runtime>(
113 ApplicationHandle:&tauri::AppHandle<R>,
114 State:&crate::ApplicationState::State::WorkspaceState::WorkspaceState::State,
115 Folders:Vec<WorkspaceFolderStateDTO>,
116) {
117 let (Added, Removed) = State.SetWorkspaceFoldersReturnDelta(Folders);
122 if Added.is_empty() && Removed.is_empty() {
123 return;
124 }
125 let AddedWire:Vec<serde_json::Value> = Added.iter().map(FolderToWire).collect();
126 let RemovedWire:Vec<serde_json::Value> = Removed.iter().map(FolderToWire).collect();
127 let BroadcastPayload = serde_json::json!({
128 "added": AddedWire.clone(),
129 "removed": RemovedWire.clone(),
130 "folders": State
131 .GetWorkspaceFolders()
132 .iter()
133 .map(FolderToWire)
134 .collect::<Vec<_>>(),
135 });
136 if let Err(Error) = LogSkyEmit(ApplicationHandle, SkyEvent::WorkspacesChanged.AsStr(), BroadcastPayload) {
137 dev_log!(
138 "workspaces",
139 "warn: [LandFix:WsDelta] sky://workspaces/changed emit failed: {}",
140 Error
141 );
142 }
143
144 PersistRecentlyOpened(&Added);
148
149 if let Ok(Handle) = tokio::runtime::Handle::try_current() {
150 Handle.spawn(async move {
151 DispatchDeltaWorkspaceFolders(Added, Removed).await;
152 });
153 }
154}
155
156fn PersistRecentlyOpened(Added:&[WorkspaceFolderStateDTO]) {
160 if Added.is_empty() {
161 return;
162 }
163 let Home = std::env::var("HOME")
164 .or_else(|_| std::env::var("USERPROFILE"))
165 .unwrap_or_default();
166 if Home.is_empty() {
167 return;
168 }
169 let Path = std::path::PathBuf::from(Home)
170 .join(".land")
171 .join("workspaces")
172 .join("RecentlyOpened.json");
173 let mut Current:serde_json::Map<String, serde_json::Value> = std::fs::read_to_string(&Path)
174 .ok()
175 .and_then(|Contents| serde_json::from_str::<serde_json::Value>(&Contents).ok())
176 .and_then(|V| V.as_object().cloned())
177 .unwrap_or_default();
178 let mut Workspaces = Current
179 .get("workspaces")
180 .and_then(|V| V.as_array())
181 .cloned()
182 .unwrap_or_default();
183 for Folder in Added {
184 let Uri = Folder.URI.to_string();
185 Workspaces.retain(|Entry| Entry.get("uri").and_then(|V| V.as_str()).unwrap_or("") != Uri);
186 Workspaces.insert(
187 0,
188 serde_json::json!({
189 "uri": Uri,
190 "label": Folder.GetDisplayName(),
191 }),
192 );
193 }
194 Workspaces.truncate(50);
195 Current.insert("workspaces".into(), serde_json::Value::Array(Workspaces));
196 if !Current.contains_key("files") {
197 Current.insert("files".into(), serde_json::json!([]));
198 }
199 if let Some(Parent) = Path.parent() {
200 let _ = std::fs::create_dir_all(Parent);
201 }
202 if let Ok(Serialised) = serde_json::to_vec_pretty(&serde_json::Value::Object(Current)) {
203 let _ = std::fs::write(&Path, Serialised);
204 }
205}