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;
15use crate::dev_log;
16
17use crate::{Host::HostConfig, WASM::Runtime::WASMRuntime};
18
19pub struct ExtensionManagerImpl {
21 #[allow(dead_code)]
23 wasm_runtime:Arc<WASMRuntime>,
24 config:HostConfig,
26 extensions:Arc<RwLock<HashMap<String, ExtensionInfo>>>,
28 stats:Arc<RwLock<ExtensionStats>>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ExtensionInfo {
35 pub id:String,
37 pub display_name:String,
39 pub description:String,
41 pub version:String,
43 pub publisher:String,
45 pub path:PathBuf,
47 pub entry_point:PathBuf,
49 pub activation_events:Vec<String>,
51 pub extension_type:ExtensionType,
53 pub state:ExtensionState,
55 pub capabilities:Vec<String>,
57 pub dependencies:Vec<String>,
59 pub manifest:serde_json::Value,
61 pub loaded_at:u64,
63 pub activated_at:Option<u64>,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69pub enum ExtensionType {
70 WASM,
72 Native,
74 JavaScript,
76 Unknown,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82pub enum ExtensionState {
83 Loaded,
85 Activated,
87 Deactivated,
89 Error,
91}
92
93#[derive(Debug, Clone, Default, Serialize, Deserialize)]
95pub struct ExtensionStats {
96 pub total_loaded:usize,
98 pub total_activated:usize,
100 pub total_deactivated:usize,
102 pub total_activation_time_ms:u64,
104 pub errors:u64,
106}
107
108impl ExtensionManagerImpl {
109 pub fn new(wasm_runtime:Arc<WASMRuntime>, config:HostConfig) -> Self {
111 Self {
112 wasm_runtime,
113 config,
114 extensions:Arc::new(RwLock::new(HashMap::new())),
115 stats:Arc::new(RwLock::new(ExtensionStats::default())),
116 }
117 }
118
119 pub async fn load_extension(&self, path:&PathBuf) -> Result<String> {
121 dev_log!("extensions", "Loading extension from: {:?}", path);
122
123 if !path.exists() {
125 return Err(anyhow::anyhow!("Extension path does not exist: {:?}", path));
126 }
127
128 let manifest = self.parse_manifest(path)?;
130 let extension_id = self.extract_extension_id(&manifest)?;
131
132 let extensions = self.extensions.read().await;
134 if extensions.contains_key(&extension_id) {
135 dev_log!("extensions", "warn: extension already loaded: {}", extension_id);
136 return Ok(extension_id);
137 }
138 drop(extensions);
139
140 let extension_type = self.determine_extension_type(path, &manifest)?;
142
143 let extension_info = ExtensionInfo {
145 id:extension_id.clone(),
146 display_name:manifest.get("displayName").and_then(|v| v.as_str()).unwrap_or("").to_string(),
147 description:manifest.get("description").and_then(|v| v.as_str()).unwrap_or("").to_string(),
148 version:manifest.get("version").and_then(|v| v.as_str()).unwrap_or("0.0.0").to_string(),
149 publisher:manifest.get("publisher").and_then(|v| v.as_str()).unwrap_or("").to_string(),
150 path:path.clone(),
151 entry_point:path.join(manifest.get("main").and_then(|v| v.as_str()).unwrap_or("dist/extension.js")),
152 activation_events:self.extract_activation_events(&manifest),
153 extension_type,
154 state:ExtensionState::Loaded,
155 capabilities:self.extract_capabilities(&manifest),
156 dependencies:self.extract_dependencies(&manifest),
157 manifest,
158 loaded_at:std::time::SystemTime::now()
159 .duration_since(std::time::UNIX_EPOCH)
160 .map(|d| d.as_secs())
161 .unwrap_or(0),
162 activated_at:None,
163 };
164
165 let mut extensions = self.extensions.write().await;
167 extensions.insert(extension_id.clone(), extension_info);
168
169 let mut stats = self.stats.write().await;
171 stats.total_loaded += 1;
172
173 dev_log!("extensions", "Extension loaded successfully: {}", extension_id);
174
175 Ok(extension_id)
176 }
177
178 pub async fn unload_extension(&self, extension_id:&str) -> Result<()> {
180 dev_log!("extensions", "Unloading extension: {}", extension_id);
181
182 let mut extensions = self.extensions.write().await;
183 extensions.remove(extension_id);
184
185 dev_log!("extensions", "Extension unloaded: {}", extension_id);
186
187 Ok(())
188 }
189
190 pub async fn get_extension(&self, extension_id:&str) -> Option<ExtensionInfo> {
192 self.extensions.read().await.get(extension_id).cloned()
193 }
194
195 pub async fn list_extensions(&self) -> Vec<String> { self.extensions.read().await.keys().cloned().collect() }
197
198 pub async fn list_extensions_by_state(&self, state:ExtensionState) -> Vec<ExtensionInfo> {
200 self.extensions
201 .read()
202 .await
203 .values()
204 .filter(|ext| ext.state == state)
205 .cloned()
206 .collect()
207 }
208
209 pub async fn update_state(&self, extension_id:&str, state:ExtensionState) -> Result<()> {
211 let mut extensions = self.extensions.write().await;
212 if let Some(info) = extensions.get_mut(extension_id) {
213 info.state = state;
214 if state == ExtensionState::Activated {
215 info.activated_at = Some(
216 std::time::SystemTime::now()
217 .duration_since(std::time::UNIX_EPOCH)
218 .map(|d| d.as_secs())
219 .unwrap_or(0),
220 );
221
222 let mut stats = self.stats.write().await;
223 stats.total_activated += 1;
224 } else if state == ExtensionState::Deactivated {
225 let mut stats = self.stats.write().await;
226 stats.total_deactivated += 1;
227 }
228 Ok(())
229 } else {
230 Err(anyhow::anyhow!("Extension not found: {}", extension_id))
231 }
232 }
233
234 pub async fn stats(&self) -> ExtensionStats { self.stats.read().await.clone() }
236
237 pub async fn discover_extensions(&self) -> Result<Vec<PathBuf>> {
239 dev_log!("extensions", "Discovering extensions in configured paths");
240
241 let mut extensions = Vec::new();
242
243 for discovery_path in &self.config.discovery_paths {
244 match self.discover_in_path(discovery_path).await {
245 Ok(mut found) => extensions.append(&mut found),
246 Err(e) => {
247 dev_log!("extensions", "warn: failed to discover extensions in {}: {}", discovery_path, e);
248 },
249 }
250 }
251
252 dev_log!("extensions", "Discovered {} extensions", extensions.len());
253
254 Ok(extensions)
255 }
256
257 async fn discover_in_path(&self, path:&str) -> Result<Vec<PathBuf>> {
259 let path = PathBuf::from(shellexpand::tilde(path).as_ref());
260
261 if !path.exists() {
262 return Ok(Vec::new());
263 }
264
265 let mut extensions = Vec::new();
266
267 let mut entries = tokio::fs::read_dir(&path)
269 .await
270 .context(format!("Failed to read directory: {:?}", path))?;
271
272 while let Some(entry) = entries.next_entry().await? {
273 let entry_path = entry.path();
274
275 if !entry_path.is_dir() {
277 continue;
278 }
279
280 let manifest_path = entry_path.join("package.json");
282 let alt_manifest_path = entry_path.join("manifest.json");
283
284 if manifest_path.exists() || alt_manifest_path.exists() {
285 extensions.push(entry_path.clone());
286 dev_log!("extensions", "Discovered extension: {:?}", entry_path);
287 }
288 }
289
290 Ok(extensions)
291 }
292
293 fn parse_manifest(&self, path:&Path) -> Result<serde_json::Value> {
295 let manifest_path = path.join("package.json");
296 let alt_manifest_path = path.join("manifest.json");
297
298 let manifest_content = if manifest_path.exists() {
299 tokio::runtime::Runtime::new()
300 .unwrap()
301 .block_on(tokio::fs::read_to_string(&manifest_path))
302 .context("Failed to read package.json")?
303 } else if alt_manifest_path.exists() {
304 tokio::runtime::Runtime::new()
305 .unwrap()
306 .block_on(tokio::fs::read_to_string(&alt_manifest_path))
307 .context("Failed to read manifest.json")?
308 } else {
309 return Err(anyhow::anyhow!("No manifest found in extension path"));
310 };
311
312 let manifest:serde_json::Value = serde_json::from_str(&manifest_content).context("Failed to parse manifest")?;
313
314 Ok(manifest)
315 }
316
317 fn extract_extension_id(&self, manifest:&serde_json::Value) -> Result<String> {
319 let publisher = manifest
320 .get("publisher")
321 .and_then(|v| v.as_str())
322 .ok_or_else(|| anyhow::anyhow!("Missing publisher in manifest"))?;
323
324 let name = manifest
325 .get("name")
326 .and_then(|v| v.as_str())
327 .ok_or_else(|| anyhow::anyhow!("Missing name in manifest"))?;
328
329 Ok(format!("{}.{}", publisher, name))
330 }
331
332 fn determine_extension_type(&self, path:&Path, manifest:&serde_json::Value) -> Result<ExtensionType> {
334 let wasm_path = path.join("extension.wasm");
336 if wasm_path.exists() {
337 return Ok(ExtensionType::WASM);
338 }
339
340 let cargo_path = path.join("Cargo.toml");
342 if cargo_path.exists() {
343 return Ok(ExtensionType::Native);
344 }
345
346 let main = manifest.get("main").and_then(|v| v.as_str());
348 if let Some(main) = main {
349 let main_path = path.join(main);
350 if main_path.exists() && (main.ends_with(".js") || main.ends_with(".ts")) {
351 return Ok(ExtensionType::JavaScript);
352 }
353 }
354
355 Ok(ExtensionType::Unknown)
356 }
357
358 fn extract_activation_events(&self, manifest:&serde_json::Value) -> Vec<String> {
360 manifest
361 .get("activationEvents")
362 .and_then(|v| v.as_array())
363 .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
364 .unwrap_or_default()
365 }
366
367 fn extract_capabilities(&self, manifest:&serde_json::Value) -> Vec<String> {
369 manifest
370 .get("capabilities")
371 .and_then(|v| v.as_object())
372 .map(|obj| obj.keys().cloned().collect())
373 .unwrap_or_default()
374 }
375
376 fn extract_dependencies(&self, manifest:&serde_json::Value) -> Vec<String> {
378 manifest
379 .get("extensionDependencies")
380 .and_then(|v| v.as_array())
381 .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
382 .unwrap_or_default()
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
391 fn test_extension_type() {
392 assert_eq!(ExtensionType::WASM, ExtensionType::WASM);
393 assert_eq!(ExtensionType::Native, ExtensionType::Native);
394 assert_eq!(ExtensionType::JavaScript, ExtensionType::JavaScript);
395 }
396
397 #[test]
398 fn test_extension_state() {
399 assert_eq!(ExtensionState::Loaded, ExtensionState::Loaded);
400 assert_eq!(ExtensionState::Activated, ExtensionState::Activated);
401 assert_eq!(ExtensionState::Deactivated, ExtensionState::Deactivated);
402 assert_eq!(ExtensionState::Error, ExtensionState::Error);
403 }
404
405 #[tokio::test]
406 async fn test_extension_manager_creation() {
407 let wasm_runtime = Arc::new(
408 tokio::runtime::Runtime::new()
409 .unwrap()
410 .block_on(crate::WASM::Runtime::WASMRuntime::new(
411 crate::WASM::Runtime::WASMConfig::default(),
412 ))
413 .unwrap(),
414 );
415 let config = HostConfig::default();
416 let manager = ExtensionManagerImpl::new(wasm_runtime, config);
417
418 assert_eq!(manager.list_extensions().await.len(), 0);
419 }
420
421 #[test]
422 fn test_extension_stats_default() {
423 let stats = ExtensionStats::default();
424 assert_eq!(stats.total_loaded, 0);
425 assert_eq!(stats.total_activated, 0);
426 }
427}