DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/IPC/WindServiceHandlers/Extensions/
ExtensionsGetInstalled.rs1use std::{
33 sync::{Arc, OnceLock},
34 time::Duration,
35};
36
37use CommonLibrary::ExtensionManagement::ExtensionManagementService::ExtensionManagementService;
38use serde_json::{Value, json};
39
40use crate::{
41 IPC::UriComponents::Normalize::Fn as NormalizeUri,
42 RunTime::ApplicationRunTime::ApplicationRunTime,
43 dev_log,
44};
45
46const EXTENSION_TYPE_SYSTEM:u8 = 0;
47
48const EXTENSION_TYPE_USER:u8 = 1;
49
50const SCAN_WAIT_CAP_MS:u64 = 5000;
51
52static INSTALLED_CACHE:[OnceLock<Value>; 3] = [OnceLock::new(), OnceLock::new(), OnceLock::new()];
57
58fn CacheIndex(TypeFilter:Option<u8>) -> usize {
59 match TypeFilter {
60 None => 0,
61
62 Some(EXTENSION_TYPE_SYSTEM) => 1,
63
64 Some(EXTENSION_TYPE_USER) => 2,
65
66 Some(_) => 0,
67 }
68}
69
70pub async fn Fn(RunTime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
71 let TypeFilter:Option<u8> = Arguments.first().and_then(|V| V.as_u64()).map(|N| N as u8);
72
73 let CacheSlot = CacheIndex(TypeFilter);
76
77 if let Some(Cached) = INSTALLED_CACHE[CacheSlot].get() {
78 let Count = Cached.as_array().map(|A| A.len()).unwrap_or(0);
79
80 dev_log!(
81 "extensions",
82 "extensions:getInstalled type={:?} returning {} entries (cache hit)",
83 TypeFilter,
84 Count
85 );
86
87 return Ok(Cached.clone());
88 }
89
90 let ScanReady = RunTime.Environment.ApplicationState.Extension.ScanReady.clone();
95 let NotifyFuture = ScanReady.notified();
96
97 let mut Extensions = RunTime
98 .Environment
99 .GetExtensions()
100 .await
101 .map_err(|Error| format!("extensions:getInstalled failed: {}", Error))?;
102
103 if Extensions.is_empty() {
104 let Notified = tokio::time::timeout(Duration::from_millis(SCAN_WAIT_CAP_MS), NotifyFuture).await;
105
106 Extensions = RunTime
107 .Environment
108 .GetExtensions()
109 .await
110 .map_err(|Error| format!("extensions:getInstalled failed: {}", Error))?;
111
112 match Notified {
113 Ok(()) => {
114 dev_log!(
115 "extensions",
116 "extensions:getInstalled: scan-ready signal received, {} entries available",
117 Extensions.len()
118 );
119 },
120
121 Err(_) => {
122 dev_log!(
123 "extensions",
124 "warn: extensions:getInstalled: scan-ready timed out after {}ms; {} entries available",
125 SCAN_WAIT_CAP_MS,
126 Extensions.len()
127 );
128 },
129 }
130 }
131
132 let Wrapped:Vec<Value> = Extensions
133 .into_iter()
134 .filter_map(|Manifest| {
135 let IsBuiltin = Manifest.get("isBuiltin").and_then(Value::as_bool).unwrap_or(true);
136 let ExtensionType = if IsBuiltin { EXTENSION_TYPE_SYSTEM } else { EXTENSION_TYPE_USER };
137
138 if let Some(Wanted) = TypeFilter
139 && Wanted != ExtensionType
140 {
141 return None;
142 }
143
144 let Publisher = Manifest
145 .get("publisher")
146 .and_then(Value::as_str)
147 .filter(|S| !S.is_empty())
148 .unwrap_or("unknown")
149 .to_string();
150 let Name = Manifest
151 .get("name")
152 .and_then(Value::as_str)
153 .filter(|S| !S.is_empty())
154 .unwrap_or("unknown")
155 .to_string();
156 let Id = format!("{}.{}", Publisher, Name);
157
158 let Location = NormalizeUri(Manifest.get("extensionLocation"));
159
160 let mut Manifest = match Manifest {
161 Value::Object(_) => Manifest,
162 _ => json!({}),
163 };
164 if let Value::Object(ref mut Map) = Manifest {
165 Map.insert("extensionLocation".to_string(), Location.clone());
166 Map.entry("publisher".to_string()).or_insert_with(|| json!(Publisher.clone()));
167 Map.entry("name".to_string()).or_insert_with(|| json!(Name.clone()));
168 Map.entry("version".to_string()).or_insert_with(|| json!("0.0.0"));
169 }
170
171 Some(json!({
172 "type": ExtensionType,
173 "isBuiltin": IsBuiltin,
174 "identifier": { "id": Id },
175 "manifest": Manifest,
176 "location": Location,
177 "targetPlatform": "undefined",
178 "isValid": true,
179 "validations": [],
180 "preRelease": false,
181 "isWorkspaceScoped": false,
182 "isMachineScoped": false,
183 "isApplicationScoped": false,
184 "publisherId": null,
185 "isPreReleaseVersion": false,
186 "hasPreReleaseVersion": false,
187 "private": false,
188 "updated": false,
189 "pinned": false,
190 "forceAutoUpdate": false,
191 "source": if IsBuiltin { "system" } else { "vsix" },
192 "size": 0,
193 }))
194 })
195 .collect();
196
197 dev_log!(
198 "extensions",
199 "extensions:getInstalled type={:?} returning {} ILocalExtension-shaped entries",
200 TypeFilter,
201 Wrapped.len()
202 );
203
204 let Response = json!(Wrapped);
205
206 if !Wrapped.is_empty() {
209 let _ = INSTALLED_CACHE[CacheSlot].set(Response.clone());
210 }
211
212 Ok(Response)
213}