1use std::{
7 collections::HashMap,
8 path::{Path, PathBuf},
9 sync::Arc,
10};
11
12use anyhow::{Context, Result};
13use serde::{Deserialize, Serialize};
14use tokio::sync::RwLock;
15
16use crate::{Host::HostConfig, WASM::Runtime::WASMRuntime, dev_log};
17
18pub struct ExtensionManagerImpl {
20 #[allow(dead_code)]
22 wasm_runtime:Arc<WASMRuntime>,
23
24 config:HostConfig,
26
27 extensions:Arc<RwLock<HashMap<String, ExtensionInfo>>>,
29
30 stats:Arc<RwLock<ExtensionStats>>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ExtensionInfo {
37 pub id:String,
39
40 pub display_name:String,
42
43 pub description:String,
45
46 pub version:String,
48
49 pub publisher:String,
51
52 pub path:PathBuf,
54
55 pub entry_point:PathBuf,
57
58 pub activation_events:Vec<String>,
60
61 pub extension_type:ExtensionType,
63
64 pub state:ExtensionState,
66
67 pub capabilities:Vec<String>,
69
70 pub dependencies:Vec<String>,
72
73 pub manifest:serde_json::Value,
75
76 pub loaded_at:u64,
78
79 pub activated_at:Option<u64>,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
85pub enum ExtensionType {
86 WASM,
88
89 Native,
91
92 JavaScript,
94
95 Unknown,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
101pub enum ExtensionState {
102 Loaded,
104
105 Activated,
107
108 Deactivated,
110
111 Error,
113}
114
115#[derive(Debug, Clone, Default, Serialize, Deserialize)]
117pub struct ExtensionStats {
118 pub total_loaded:usize,
120
121 pub total_activated:usize,
123
124 pub total_deactivated:usize,
126
127 pub total_activation_time_ms:u64,
129
130 pub errors:u64,
132}
133
134impl ExtensionManagerImpl {
135 pub fn new(wasm_runtime:Arc<WASMRuntime>, config:HostConfig) -> Self {
137 Self {
138 wasm_runtime,
139
140 config,
141
142 extensions:Arc::new(RwLock::new(HashMap::new())),
143
144 stats:Arc::new(RwLock::new(ExtensionStats::default())),
145 }
146 }
147
148 pub async fn load_extension(&self, path:&PathBuf) -> Result<String> {
150 dev_log!("extensions", "Loading extension from: {:?}", path);
151
152 if !path.exists() {
154 return Err(anyhow::anyhow!("Extension path does not exist: {:?}", path));
155 }
156
157 let manifest = self.parse_manifest(path)?;
159
160 let extension_id = self.extract_extension_id(&manifest)?;
161
162 let extensions = self.extensions.read().await;
164
165 if extensions.contains_key(&extension_id) {
166 dev_log!("extensions", "warn: extension already loaded: {}", extension_id);
167
168 return Ok(extension_id);
169 }
170
171 drop(extensions);
172
173 let extension_type = self.determine_extension_type(path, &manifest)?;
175
176 let extension_info = ExtensionInfo {
178 id:extension_id.clone(),
179
180 display_name:manifest.get("displayName").and_then(|v| v.as_str()).unwrap_or("").to_string(),
181
182 description:manifest.get("description").and_then(|v| v.as_str()).unwrap_or("").to_string(),
183
184 version:manifest.get("version").and_then(|v| v.as_str()).unwrap_or("0.0.0").to_string(),
185
186 publisher:manifest.get("publisher").and_then(|v| v.as_str()).unwrap_or("").to_string(),
187
188 path:path.clone(),
189
190 entry_point:path.join(manifest.get("main").and_then(|v| v.as_str()).unwrap_or("dist/extension.js")),
191
192 activation_events:self.extract_activation_events(&manifest),
193
194 extension_type,
195
196 state:ExtensionState::Loaded,
197
198 capabilities:self.extract_capabilities(&manifest),
199
200 dependencies:self.extract_dependencies(&manifest),
201
202 manifest,
203
204 loaded_at:std::time::SystemTime::now()
205 .duration_since(std::time::UNIX_EPOCH)
206 .map(|d| d.as_secs())
207 .unwrap_or(0),
208
209 activated_at:None,
210 };
211
212 let mut extensions = self.extensions.write().await;
214
215 extensions.insert(extension_id.clone(), extension_info);
216
217 let mut stats = self.stats.write().await;
219
220 stats.total_loaded += 1;
221
222 dev_log!("extensions", "Extension loaded successfully: {}", extension_id);
223
224 Ok(extension_id)
225 }
226
227 pub async fn unload_extension(&self, extension_id:&str) -> Result<()> {
229 dev_log!("extensions", "Unloading extension: {}", extension_id);
230
231 let mut extensions = self.extensions.write().await;
232
233 extensions.remove(extension_id);
234
235 dev_log!("extensions", "Extension unloaded: {}", extension_id);
236
237 Ok(())
238 }
239
240 pub async fn get_extension(&self, extension_id:&str) -> Option<ExtensionInfo> {
242 self.extensions.read().await.get(extension_id).cloned()
243 }
244
245 pub async fn list_extensions(&self) -> Vec<String> { self.extensions.read().await.keys().cloned().collect() }
247
248 pub async fn list_extensions_by_state(&self, state:ExtensionState) -> Vec<ExtensionInfo> {
250 self.extensions
251 .read()
252 .await
253 .values()
254 .filter(|ext| ext.state == state)
255 .cloned()
256 .collect()
257 }
258
259 pub async fn update_state(&self, extension_id:&str, state:ExtensionState) -> Result<()> {
261 let mut extensions = self.extensions.write().await;
262
263 if let Some(info) = extensions.get_mut(extension_id) {
264 info.state = state;
265
266 if state == ExtensionState::Activated {
267 info.activated_at = Some(
268 std::time::SystemTime::now()
269 .duration_since(std::time::UNIX_EPOCH)
270 .map(|d| d.as_secs())
271 .unwrap_or(0),
272 );
273
274 let mut stats = self.stats.write().await;
275
276 stats.total_activated += 1;
277 } else if state == ExtensionState::Deactivated {
278 let mut stats = self.stats.write().await;
279
280 stats.total_deactivated += 1;
281 }
282
283 Ok(())
284 } else {
285 Err(anyhow::anyhow!("Extension not found: {}", extension_id))
286 }
287 }
288
289 pub async fn stats(&self) -> ExtensionStats { self.stats.read().await.clone() }
291
292 pub async fn discover_extensions(&self) -> Result<Vec<PathBuf>> {
294 dev_log!("extensions", "Discovering extensions in configured paths");
295
296 let mut extensions = Vec::new();
297
298 for discovery_path in &self.config.discovery_paths {
299 match self.discover_in_path(discovery_path).await {
300 Ok(mut found) => extensions.append(&mut found),
301
302 Err(e) => {
303 dev_log!("extensions", "warn: failed to discover extensions in {}: {}", discovery_path, e);
304 },
305 }
306 }
307
308 dev_log!("extensions", "Discovered {} extensions", extensions.len());
309
310 Ok(extensions)
311 }
312
313 async fn discover_in_path(&self, path:&str) -> Result<Vec<PathBuf>> {
315 let path = PathBuf::from(shellexpand::tilde(path).as_ref());
316
317 if !path.exists() {
318 return Ok(Vec::new());
319 }
320
321 let mut extensions = Vec::new();
322
323 let mut entries = tokio::fs::read_dir(&path)
325 .await
326 .context(format!("Failed to read directory: {:?}", path))?;
327
328 while let Some(entry) = entries.next_entry().await? {
329 let entry_path = entry.path();
330
331 if !entry_path.is_dir() {
333 continue;
334 }
335
336 let manifest_path = entry_path.join("package.json");
338
339 let alt_manifest_path = entry_path.join("manifest.json");
340
341 if manifest_path.exists() || alt_manifest_path.exists() {
342 extensions.push(entry_path.clone());
343
344 dev_log!("extensions", "Discovered extension: {:?}", entry_path);
345 }
346 }
347
348 Ok(extensions)
349 }
350
351 fn parse_manifest(&self, path:&Path) -> Result<serde_json::Value> {
353 let manifest_path = path.join("package.json");
354
355 let alt_manifest_path = path.join("manifest.json");
356
357 let manifest_content = if manifest_path.exists() {
358 tokio::runtime::Runtime::new()
359 .unwrap()
360 .block_on(tokio::fs::read_to_string(&manifest_path))
361 .context("Failed to read package.json")?
362 } else if alt_manifest_path.exists() {
363 tokio::runtime::Runtime::new()
364 .unwrap()
365 .block_on(tokio::fs::read_to_string(&alt_manifest_path))
366 .context("Failed to read manifest.json")?
367 } else {
368 return Err(anyhow::anyhow!("No manifest found in extension path"));
369 };
370
371 let manifest:serde_json::Value = serde_json::from_str(&manifest_content).context("Failed to parse manifest")?;
372
373 Ok(manifest)
374 }
375
376 fn extract_extension_id(&self, manifest:&serde_json::Value) -> Result<String> {
378 let publisher = manifest
379 .get("publisher")
380 .and_then(|v| v.as_str())
381 .ok_or_else(|| anyhow::anyhow!("Missing publisher in manifest"))?;
382
383 let name = manifest
384 .get("name")
385 .and_then(|v| v.as_str())
386 .ok_or_else(|| anyhow::anyhow!("Missing name in manifest"))?;
387
388 Ok(format!("{}.{}", publisher, name))
389 }
390
391 fn determine_extension_type(&self, path:&Path, manifest:&serde_json::Value) -> Result<ExtensionType> {
393 let wasm_path = path.join("extension.wasm");
395
396 if wasm_path.exists() {
397 return Ok(ExtensionType::WASM);
398 }
399
400 let cargo_path = path.join("Cargo.toml");
402
403 if cargo_path.exists() {
404 return Ok(ExtensionType::Native);
405 }
406
407 let main = manifest.get("main").and_then(|v| v.as_str());
409
410 if let Some(main) = main {
411 let main_path = path.join(main);
412
413 if main_path.exists() && (main.ends_with(".js") || main.ends_with(".ts")) {
414 return Ok(ExtensionType::JavaScript);
415 }
416 }
417
418 Ok(ExtensionType::Unknown)
419 }
420
421 fn extract_activation_events(&self, manifest:&serde_json::Value) -> Vec<String> {
423 manifest
424 .get("activationEvents")
425 .and_then(|v| v.as_array())
426 .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
427 .unwrap_or_default()
428 }
429
430 fn extract_capabilities(&self, manifest:&serde_json::Value) -> Vec<String> {
432 manifest
433 .get("capabilities")
434 .and_then(|v| v.as_object())
435 .map(|obj| obj.keys().cloned().collect())
436 .unwrap_or_default()
437 }
438
439 fn extract_dependencies(&self, manifest:&serde_json::Value) -> Vec<String> {
441 manifest
442 .get("extensionDependencies")
443 .and_then(|v| v.as_array())
444 .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
445 .unwrap_or_default()
446 }
447}
448
449#[cfg(test)]
450mod tests {
451
452 use super::*;
453
454 #[test]
455 fn test_extension_type() {
456 assert_eq!(ExtensionType::WASM, ExtensionType::WASM);
457
458 assert_eq!(ExtensionType::Native, ExtensionType::Native);
459
460 assert_eq!(ExtensionType::JavaScript, ExtensionType::JavaScript);
461 }
462
463 #[test]
464 fn test_extension_state() {
465 assert_eq!(ExtensionState::Loaded, ExtensionState::Loaded);
466
467 assert_eq!(ExtensionState::Activated, ExtensionState::Activated);
468
469 assert_eq!(ExtensionState::Deactivated, ExtensionState::Deactivated);
470
471 assert_eq!(ExtensionState::Error, ExtensionState::Error);
472 }
473
474 #[tokio::test]
475 async fn test_extension_manager_creation() {
476 let wasm_runtime = Arc::new(
477 tokio::runtime::Runtime::new()
478 .unwrap()
479 .block_on(crate::WASM::Runtime::WASMRuntime::new(
480 crate::WASM::Runtime::WASMConfig::default(),
481 ))
482 .unwrap(),
483 );
484
485 let config = HostConfig::default();
486
487 let manager = ExtensionManagerImpl::new(wasm_runtime, config);
488
489 assert_eq!(manager.list_extensions().await.len(), 0);
490 }
491
492 #[test]
493 fn test_extension_stats_default() {
494 let stats = ExtensionStats::default();
495
496 assert_eq!(stats.total_loaded, 0);
497
498 assert_eq!(stats.total_activated, 0);
499 }
500}