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 config:HostConfig,
25 extensions:Arc<RwLock<HashMap<String, ExtensionInfo>>>,
27 stats:Arc<RwLock<ExtensionStats>>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct ExtensionInfo {
34 pub id:String,
36 pub display_name:String,
38 pub description:String,
40 pub version:String,
42 pub publisher:String,
44 pub path:PathBuf,
46 pub entry_point:PathBuf,
48 pub activation_events:Vec<String>,
50 pub extension_type:ExtensionType,
52 pub state:ExtensionState,
54 pub capabilities:Vec<String>,
56 pub dependencies:Vec<String>,
58 pub manifest:serde_json::Value,
60 pub loaded_at:u64,
62 pub activated_at:Option<u64>,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
68pub enum ExtensionType {
69 WASM,
71 Native,
73 JavaScript,
75 Unknown,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
81pub enum ExtensionState {
82 Loaded,
84 Activated,
86 Deactivated,
88 Error,
90}
91
92#[derive(Debug, Clone, Default, Serialize, Deserialize)]
94pub struct ExtensionStats {
95 pub total_loaded:usize,
97 pub total_activated:usize,
99 pub total_deactivated:usize,
101 pub total_activation_time_ms:u64,
103 pub errors:u64,
105}
106
107impl ExtensionManagerImpl {
108 pub fn new(wasm_runtime:Arc<WASMRuntime>, config:HostConfig) -> Self {
110 Self {
111 wasm_runtime,
112 config,
113 extensions:Arc::new(RwLock::new(HashMap::new())),
114 stats:Arc::new(RwLock::new(ExtensionStats::default())),
115 }
116 }
117
118 pub async fn load_extension(&self, path:&PathBuf) -> Result<String> {
120 dev_log!("extensions", "Loading extension from: {:?}", path);
121
122 if !path.exists() {
124 return Err(anyhow::anyhow!("Extension path does not exist: {:?}", path));
125 }
126
127 let manifest = self.parse_manifest(path)?;
129 let extension_id = self.extract_extension_id(&manifest)?;
130
131 let extensions = self.extensions.read().await;
133 if extensions.contains_key(&extension_id) {
134 dev_log!("extensions", "warn: extension already loaded: {}", extension_id);
135 return Ok(extension_id);
136 }
137 drop(extensions);
138
139 let extension_type = self.determine_extension_type(path, &manifest)?;
141
142 let extension_info = ExtensionInfo {
144 id:extension_id.clone(),
145 display_name:manifest.get("displayName").and_then(|v| v.as_str()).unwrap_or("").to_string(),
146 description:manifest.get("description").and_then(|v| v.as_str()).unwrap_or("").to_string(),
147 version:manifest.get("version").and_then(|v| v.as_str()).unwrap_or("0.0.0").to_string(),
148 publisher:manifest.get("publisher").and_then(|v| v.as_str()).unwrap_or("").to_string(),
149 path:path.clone(),
150 entry_point:path.join(manifest.get("main").and_then(|v| v.as_str()).unwrap_or("dist/extension.js")),
151 activation_events:self.extract_activation_events(&manifest),
152 extension_type,
153 state:ExtensionState::Loaded,
154 capabilities:self.extract_capabilities(&manifest),
155 dependencies:self.extract_dependencies(&manifest),
156 manifest,
157 loaded_at:std::time::SystemTime::now()
158 .duration_since(std::time::UNIX_EPOCH)
159 .map(|d| d.as_secs())
160 .unwrap_or(0),
161 activated_at:None,
162 };
163
164 let mut extensions = self.extensions.write().await;
166 extensions.insert(extension_id.clone(), extension_info);
167
168 let mut stats = self.stats.write().await;
170 stats.total_loaded += 1;
171
172 dev_log!("extensions", "Extension loaded successfully: {}", extension_id);
173
174 Ok(extension_id)
175 }
176
177 pub async fn unload_extension(&self, extension_id:&str) -> Result<()> {
179 dev_log!("extensions", "Unloading extension: {}", extension_id);
180
181 let mut extensions = self.extensions.write().await;
182 extensions.remove(extension_id);
183
184 dev_log!("extensions", "Extension unloaded: {}", extension_id);
185
186 Ok(())
187 }
188
189 pub async fn get_extension(&self, extension_id:&str) -> Option<ExtensionInfo> {
191 self.extensions.read().await.get(extension_id).cloned()
192 }
193
194 pub async fn list_extensions(&self) -> Vec<String> { self.extensions.read().await.keys().cloned().collect() }
196
197 pub async fn list_extensions_by_state(&self, state:ExtensionState) -> Vec<ExtensionInfo> {
199 self.extensions
200 .read()
201 .await
202 .values()
203 .filter(|ext| ext.state == state)
204 .cloned()
205 .collect()
206 }
207
208 pub async fn update_state(&self, extension_id:&str, state:ExtensionState) -> Result<()> {
210 let mut extensions = self.extensions.write().await;
211 if let Some(info) = extensions.get_mut(extension_id) {
212 info.state = state;
213 if state == ExtensionState::Activated {
214 info.activated_at = Some(
215 std::time::SystemTime::now()
216 .duration_since(std::time::UNIX_EPOCH)
217 .map(|d| d.as_secs())
218 .unwrap_or(0),
219 );
220
221 let mut stats = self.stats.write().await;
222 stats.total_activated += 1;
223 } else if state == ExtensionState::Deactivated {
224 let mut stats = self.stats.write().await;
225 stats.total_deactivated += 1;
226 }
227 Ok(())
228 } else {
229 Err(anyhow::anyhow!("Extension not found: {}", extension_id))
230 }
231 }
232
233 pub async fn stats(&self) -> ExtensionStats { self.stats.read().await.clone() }
235
236 pub async fn discover_extensions(&self) -> Result<Vec<PathBuf>> {
238 dev_log!("extensions", "Discovering extensions in configured paths");
239
240 let mut extensions = Vec::new();
241
242 for discovery_path in &self.config.discovery_paths {
243 match self.discover_in_path(discovery_path).await {
244 Ok(mut found) => extensions.append(&mut found),
245 Err(e) => {
246 dev_log!("extensions", "warn: failed to discover extensions in {}: {}", discovery_path, e);
247 },
248 }
249 }
250
251 dev_log!("extensions", "Discovered {} extensions", extensions.len());
252
253 Ok(extensions)
254 }
255
256 async fn discover_in_path(&self, path:&str) -> Result<Vec<PathBuf>> {
258 let path = PathBuf::from(shellexpand::tilde(path).as_ref());
259
260 if !path.exists() {
261 return Ok(Vec::new());
262 }
263
264 let mut extensions = Vec::new();
265
266 let mut entries = tokio::fs::read_dir(&path)
268 .await
269 .context(format!("Failed to read directory: {:?}", path))?;
270
271 while let Some(entry) = entries.next_entry().await? {
272 let entry_path = entry.path();
273
274 if !entry_path.is_dir() {
276 continue;
277 }
278
279 let manifest_path = entry_path.join("package.json");
281 let alt_manifest_path = entry_path.join("manifest.json");
282
283 if manifest_path.exists() || alt_manifest_path.exists() {
284 extensions.push(entry_path.clone());
285 dev_log!("extensions", "Discovered extension: {:?}", entry_path);
286 }
287 }
288
289 Ok(extensions)
290 }
291
292 fn parse_manifest(&self, path:&Path) -> Result<serde_json::Value> {
294 let manifest_path = path.join("package.json");
295 let alt_manifest_path = path.join("manifest.json");
296
297 let manifest_content = if manifest_path.exists() {
298 tokio::runtime::Runtime::new()
299 .unwrap()
300 .block_on(tokio::fs::read_to_string(&manifest_path))
301 .context("Failed to read package.json")?
302 } else if alt_manifest_path.exists() {
303 tokio::runtime::Runtime::new()
304 .unwrap()
305 .block_on(tokio::fs::read_to_string(&alt_manifest_path))
306 .context("Failed to read manifest.json")?
307 } else {
308 return Err(anyhow::anyhow!("No manifest found in extension path"));
309 };
310
311 let manifest:serde_json::Value = serde_json::from_str(&manifest_content).context("Failed to parse manifest")?;
312
313 Ok(manifest)
314 }
315
316 fn extract_extension_id(&self, manifest:&serde_json::Value) -> Result<String> {
318 let publisher = manifest
319 .get("publisher")
320 .and_then(|v| v.as_str())
321 .ok_or_else(|| anyhow::anyhow!("Missing publisher in manifest"))?;
322
323 let name = manifest
324 .get("name")
325 .and_then(|v| v.as_str())
326 .ok_or_else(|| anyhow::anyhow!("Missing name in manifest"))?;
327
328 Ok(format!("{}.{}", publisher, name))
329 }
330
331 fn determine_extension_type(&self, path:&Path, manifest:&serde_json::Value) -> Result<ExtensionType> {
333 let wasm_path = path.join("extension.wasm");
335 if wasm_path.exists() {
336 return Ok(ExtensionType::WASM);
337 }
338
339 let cargo_path = path.join("Cargo.toml");
341 if cargo_path.exists() {
342 return Ok(ExtensionType::Native);
343 }
344
345 let main = manifest.get("main").and_then(|v| v.as_str());
347 if let Some(main) = main {
348 let main_path = path.join(main);
349 if main_path.exists() && (main.ends_with(".js") || main.ends_with(".ts")) {
350 return Ok(ExtensionType::JavaScript);
351 }
352 }
353
354 Ok(ExtensionType::Unknown)
355 }
356
357 fn extract_activation_events(&self, manifest:&serde_json::Value) -> Vec<String> {
359 manifest
360 .get("activationEvents")
361 .and_then(|v| v.as_array())
362 .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
363 .unwrap_or_default()
364 }
365
366 fn extract_capabilities(&self, manifest:&serde_json::Value) -> Vec<String> {
368 manifest
369 .get("capabilities")
370 .and_then(|v| v.as_object())
371 .map(|obj| obj.keys().cloned().collect())
372 .unwrap_or_default()
373 }
374
375 fn extract_dependencies(&self, manifest:&serde_json::Value) -> Vec<String> {
377 manifest
378 .get("extensionDependencies")
379 .and_then(|v| v.as_array())
380 .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
381 .unwrap_or_default()
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388
389 #[test]
390 fn test_extension_type() {
391 assert_eq!(ExtensionType::WASM, ExtensionType::WASM);
392 assert_eq!(ExtensionType::Native, ExtensionType::Native);
393 assert_eq!(ExtensionType::JavaScript, ExtensionType::JavaScript);
394 }
395
396 #[test]
397 fn test_extension_state() {
398 assert_eq!(ExtensionState::Loaded, ExtensionState::Loaded);
399 assert_eq!(ExtensionState::Activated, ExtensionState::Activated);
400 assert_eq!(ExtensionState::Deactivated, ExtensionState::Deactivated);
401 assert_eq!(ExtensionState::Error, ExtensionState::Error);
402 }
403
404 #[tokio::test]
405 async fn test_extension_manager_creation() {
406 let wasm_runtime = Arc::new(
407 tokio::runtime::Runtime::new()
408 .unwrap()
409 .block_on(crate::WASM::Runtime::WASMRuntime::new(
410 crate::WASM::Runtime::WASMConfig::default(),
411 ))
412 .unwrap(),
413 );
414 let config = HostConfig::default();
415 let manager = ExtensionManagerImpl::new(wasm_runtime, config);
416
417 assert_eq!(manager.list_extensions().await.len(), 0);
418 }
419
420 #[test]
421 fn test_extension_stats_default() {
422 let stats = ExtensionStats::default();
423 assert_eq!(stats.total_loaded, 0);
424 assert_eq!(stats.total_activated, 0);
425 }
426}