1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::path::PathBuf;
8use std::sync::Arc;
9use tokio::sync::RwLock;
10use tracing::{debug, info};
11
12use super::state::PluginState;
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct PluginVersion {
17 pub major: u32,
19 pub minor: u32,
21 pub patch: u32,
23 pub prerelease: Option<String>,
25 pub build: Option<String>,
27}
28
29impl PluginVersion {
30 pub fn new(major: u32, minor: u32, patch: u32) -> Self {
32 Self {
33 major,
34 minor,
35 patch,
36 prerelease: None,
37 build: None,
38 }
39 }
40
41 pub fn parse(version: &str) -> Result<Self, String> {
43 let version = version.trim();
44
45 let (version_pre, build) = if let Some(idx) = version.find('+') {
47 (&version[..idx], Some(version[idx + 1..].to_string()))
48 } else {
49 (version, None)
50 };
51
52 let (version_core, prerelease) = if let Some(idx) = version_pre.find('-') {
54 (
55 &version_pre[..idx],
56 Some(version_pre[idx + 1..].to_string()),
57 )
58 } else {
59 (version_pre, None)
60 };
61
62 let parts: Vec<&str> = version_core.split('.').collect();
64 if parts.len() < 2 || parts.len() > 3 {
65 return Err(format!("Invalid version format: {}", version));
66 }
67
68 let major = parts[0]
69 .parse::<u32>()
70 .map_err(|_| format!("Invalid major version: {}", parts[0]))?;
71 let minor = parts[1]
72 .parse::<u32>()
73 .map_err(|_| format!("Invalid minor version: {}", parts[1]))?;
74 let patch = if parts.len() > 2 {
75 parts[2]
76 .parse::<u32>()
77 .map_err(|_| format!("Invalid patch version: {}", parts[2]))?
78 } else {
79 0
80 };
81
82 Ok(Self {
83 major,
84 minor,
85 patch,
86 prerelease,
87 build,
88 })
89 }
90
91 pub fn is_compatible(&self, other: &PluginVersion) -> bool {
93 self.major == other.major
94 }
95
96 pub fn is_newer_than(&self, other: &PluginVersion) -> bool {
98 if self.major != other.major {
99 return self.major > other.major;
100 }
101 if self.minor != other.minor {
102 return self.minor > other.minor;
103 }
104 self.patch > other.patch
105 }
106}
107
108impl std::fmt::Display for PluginVersion {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
111 if let Some(ref pre) = self.prerelease {
112 write!(f, "-{}", pre)?;
113 }
114 if let Some(ref build) = self.build {
115 write!(f, "+{}", build)?;
116 }
117 Ok(())
118 }
119}
120
121impl Default for PluginVersion {
122 fn default() -> Self {
123 Self::new(0, 0, 0)
124 }
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct PluginInfo {
130 pub id: String,
132 pub name: String,
134 pub version: PluginVersion,
136 pub description: String,
138 pub author: Option<String>,
140 pub library_path: Option<PathBuf>,
142 pub state: PluginState,
144 pub loaded_at: Option<u64>,
146 pub last_reload: Option<u64>,
148 pub reload_count: u32,
150 pub dependencies: Vec<String>,
152 pub capabilities: Vec<String>,
154 pub metadata: HashMap<String, String>,
156 pub file_hash: Option<String>,
158}
159
160impl PluginInfo {
161 pub fn new(id: &str, name: &str, version: PluginVersion) -> Self {
163 Self {
164 id: id.to_string(),
165 name: name.to_string(),
166 version,
167 description: String::new(),
168 author: None,
169 library_path: None,
170 state: PluginState::Unloaded,
171 loaded_at: None,
172 last_reload: None,
173 reload_count: 0,
174 dependencies: Vec::new(),
175 capabilities: Vec::new(),
176 metadata: HashMap::new(),
177 file_hash: None,
178 }
179 }
180
181 pub fn with_description(mut self, desc: &str) -> Self {
183 self.description = desc.to_string();
184 self
185 }
186
187 pub fn with_author(mut self, author: &str) -> Self {
189 self.author = Some(author.to_string());
190 self
191 }
192
193 pub fn with_library_path<P: AsRef<std::path::Path>>(mut self, path: P) -> Self {
195 self.library_path = Some(path.as_ref().to_path_buf());
196 self
197 }
198
199 pub fn with_dependency(mut self, dep: &str) -> Self {
201 self.dependencies.push(dep.to_string());
202 self
203 }
204
205 pub fn with_capability(mut self, cap: &str) -> Self {
207 self.capabilities.push(cap.to_string());
208 self
209 }
210
211 pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
213 self.metadata.insert(key.to_string(), value.to_string());
214 self
215 }
216
217 pub fn mark_loaded(&mut self) {
219 self.state = PluginState::Loaded;
220 self.loaded_at = Some(
221 std::time::SystemTime::now()
222 .duration_since(std::time::UNIX_EPOCH)
223 .unwrap_or_default()
224 .as_secs(),
225 );
226 }
227
228 pub fn mark_reloaded(&mut self) {
230 self.state = PluginState::Loaded;
231 self.last_reload = Some(
232 std::time::SystemTime::now()
233 .duration_since(std::time::UNIX_EPOCH)
234 .unwrap_or_default()
235 .as_secs(),
236 );
237 self.reload_count += 1;
238 }
239
240 pub fn check_dependencies(&self, available: &[String]) -> Vec<String> {
242 self.dependencies
243 .iter()
244 .filter(|dep| !available.contains(dep))
245 .cloned()
246 .collect()
247 }
248
249 pub fn has_capability(&self, cap: &str) -> bool {
251 self.capabilities.iter().any(|c| c == cap)
252 }
253}
254
255pub struct PluginRegistry {
257 plugins: Arc<RwLock<HashMap<String, PluginInfo>>>,
259 path_to_id: Arc<RwLock<HashMap<PathBuf, String>>>,
261 auto_register: bool,
263}
264
265impl PluginRegistry {
266 pub fn new() -> Self {
268 Self {
269 plugins: Arc::new(RwLock::new(HashMap::new())),
270 path_to_id: Arc::new(RwLock::new(HashMap::new())),
271 auto_register: true,
272 }
273 }
274
275 pub fn with_auto_register(mut self, enabled: bool) -> Self {
277 self.auto_register = enabled;
278 self
279 }
280
281 pub async fn register(&self, info: PluginInfo) -> Result<(), String> {
283 let plugin_id = info.id.clone();
284
285 info!("Registering plugin: {} v{}", info.name, info.version);
286
287 let mut plugins = self.plugins.write().await;
288
289 if plugins.contains_key(&plugin_id) {
290 return Err(format!("Plugin {} already registered", plugin_id));
291 }
292
293 if let Some(ref path) = info.library_path {
295 let mut path_map = self.path_to_id.write().await;
296 path_map.insert(path.clone(), plugin_id.clone());
297 }
298
299 plugins.insert(plugin_id, info);
300 Ok(())
301 }
302
303 pub async fn update(&self, info: PluginInfo) -> Result<(), String> {
305 let plugin_id = info.id.clone();
306
307 debug!("Updating plugin registration: {}", plugin_id);
308
309 let mut plugins = self.plugins.write().await;
310
311 if !plugins.contains_key(&plugin_id) {
312 return Err(format!("Plugin {} not registered", plugin_id));
313 }
314
315 if let Some(ref path) = info.library_path {
317 let mut path_map = self.path_to_id.write().await;
318 path_map.insert(path.clone(), plugin_id.clone());
319 }
320
321 plugins.insert(plugin_id, info);
322 Ok(())
323 }
324
325 pub async fn unregister(&self, plugin_id: &str) -> Result<PluginInfo, String> {
327 info!("Unregistering plugin: {}", plugin_id);
328
329 let mut plugins = self.plugins.write().await;
330
331 let info = plugins
332 .remove(plugin_id)
333 .ok_or_else(|| format!("Plugin {} not found", plugin_id))?;
334
335 if let Some(ref path) = info.library_path {
337 let mut path_map = self.path_to_id.write().await;
338 path_map.remove(path);
339 }
340
341 Ok(info)
342 }
343
344 pub async fn get(&self, plugin_id: &str) -> Option<PluginInfo> {
346 let plugins = self.plugins.read().await;
347 plugins.get(plugin_id).cloned()
348 }
349
350 pub async fn get_by_path<P: AsRef<std::path::Path>>(&self, path: P) -> Option<PluginInfo> {
352 let path = path.as_ref().to_path_buf();
353
354 let path_map = self.path_to_id.read().await;
355 if let Some(plugin_id) = path_map.get(&path) {
356 let plugins = self.plugins.read().await;
357 return plugins.get(plugin_id).cloned();
358 }
359
360 None
361 }
362
363 pub async fn contains(&self, plugin_id: &str) -> bool {
365 let plugins = self.plugins.read().await;
366 plugins.contains_key(plugin_id)
367 }
368
369 pub async fn list(&self) -> Vec<PluginInfo> {
371 let plugins = self.plugins.read().await;
372 plugins.values().cloned().collect()
373 }
374
375 pub async fn plugin_ids(&self) -> Vec<String> {
377 let plugins = self.plugins.read().await;
378 plugins.keys().cloned().collect()
379 }
380
381 pub async fn find_by_capability(&self, capability: &str) -> Vec<PluginInfo> {
383 let plugins = self.plugins.read().await;
384 plugins
385 .values()
386 .filter(|p| p.has_capability(capability))
387 .cloned()
388 .collect()
389 }
390
391 pub async fn find_by_state(&self, state: PluginState) -> Vec<PluginInfo> {
393 let plugins = self.plugins.read().await;
394 plugins
395 .values()
396 .filter(|p| p.state == state)
397 .cloned()
398 .collect()
399 }
400
401 pub async fn set_state(&self, plugin_id: &str, state: PluginState) -> Result<(), String> {
403 let mut plugins = self.plugins.write().await;
404
405 if let Some(info) = plugins.get_mut(plugin_id) {
406 debug!(
407 "Updating plugin {} state: {:?} -> {:?}",
408 plugin_id, info.state, state
409 );
410 info.state = state;
411 Ok(())
412 } else {
413 Err(format!("Plugin {} not found", plugin_id))
414 }
415 }
416
417 pub async fn get_load_order(&self) -> Result<Vec<String>, String> {
419 let plugins = self.plugins.read().await;
420
421 let mut in_degree: HashMap<String, usize> = HashMap::new();
423 let mut dependents: HashMap<String, Vec<String>> = HashMap::new();
424
425 for (id, info) in plugins.iter() {
426 in_degree.entry(id.clone()).or_insert(0);
427
428 for dep in &info.dependencies {
429 dependents.entry(dep.clone()).or_default().push(id.clone());
430 *in_degree.entry(id.clone()).or_insert(0) += 1;
431 }
432 }
433
434 let mut result = Vec::new();
436 let mut queue: Vec<String> = in_degree
437 .iter()
438 .filter(|(_, deg)| deg == &&0)
439 .map(|(id, _)| id.clone())
440 .collect();
441
442 while let Some(id) = queue.pop() {
443 result.push(id.clone());
444
445 if let Some(deps) = dependents.get(&id) {
446 for dep in deps {
447 if let Some(deg) = in_degree.get_mut(dep) {
448 *deg -= 1;
449 if *deg == 0 {
450 queue.push(dep.clone());
451 }
452 }
453 }
454 }
455 }
456
457 if result.len() != plugins.len() {
458 return Err("Circular dependency detected".to_string());
459 }
460
461 Ok(result)
462 }
463
464 pub async fn clear(&self) {
466 let mut plugins = self.plugins.write().await;
467 plugins.clear();
468
469 let mut path_map = self.path_to_id.write().await;
470 path_map.clear();
471 }
472
473 pub async fn stats(&self) -> RegistryStats {
475 let plugins = self.plugins.read().await;
476
477 let mut stats = RegistryStats::default();
478 stats.total_plugins = plugins.len();
479
480 for info in plugins.values() {
481 match info.state {
482 PluginState::Loaded | PluginState::Running => stats.loaded_plugins += 1,
483 PluginState::Failed(_) => stats.failed_plugins += 1,
484 _ => {}
485 }
486 stats.total_reloads += info.reload_count as usize;
487 }
488
489 stats
490 }
491}
492
493impl Default for PluginRegistry {
494 fn default() -> Self {
495 Self::new()
496 }
497}
498
499#[derive(Debug, Clone, Default)]
501pub struct RegistryStats {
502 pub total_plugins: usize,
504 pub loaded_plugins: usize,
506 pub failed_plugins: usize,
508 pub total_reloads: usize,
510}
511
512#[cfg(test)]
513mod tests {
514 use super::*;
515
516 #[test]
517 fn test_version_parse() {
518 let v = PluginVersion::parse("1.2.3").unwrap();
519 assert_eq!(v.major, 1);
520 assert_eq!(v.minor, 2);
521 assert_eq!(v.patch, 3);
522
523 let v = PluginVersion::parse("1.2.3-alpha").unwrap();
524 assert_eq!(v.prerelease, Some("alpha".to_string()));
525
526 let v = PluginVersion::parse("1.2.3-beta+build123").unwrap();
527 assert_eq!(v.prerelease, Some("beta".to_string()));
528 assert_eq!(v.build, Some("build123".to_string()));
529 }
530
531 #[test]
532 fn test_version_comparison() {
533 let v1 = PluginVersion::new(1, 0, 0);
534 let v2 = PluginVersion::new(1, 1, 0);
535 let v3 = PluginVersion::new(2, 0, 0);
536
537 assert!(v1.is_compatible(&v2));
538 assert!(!v1.is_compatible(&v3));
539 assert!(v2.is_newer_than(&v1));
540 assert!(v3.is_newer_than(&v2));
541 }
542
543 #[test]
544 fn test_version_display() {
545 let v = PluginVersion::new(1, 2, 3);
546 assert_eq!(v.to_string(), "1.2.3");
547
548 let mut v = PluginVersion::new(1, 0, 0);
549 v.prerelease = Some("alpha".to_string());
550 assert_eq!(v.to_string(), "1.0.0-alpha");
551 }
552
553 #[test]
554 fn test_plugin_info() {
555 let info = PluginInfo::new("test", "Test Plugin", PluginVersion::new(1, 0, 0))
556 .with_description("A test plugin")
557 .with_author("Developer")
558 .with_capability("feature_a")
559 .with_capability("feature_b");
560
561 assert_eq!(info.id, "test");
562 assert!(info.has_capability("feature_a"));
563 assert!(!info.has_capability("feature_c"));
564 }
565
566 #[tokio::test]
567 async fn test_registry() {
568 let registry = PluginRegistry::new();
569
570 let info = PluginInfo::new("plugin-1", "Plugin 1", PluginVersion::new(1, 0, 0));
571 registry.register(info).await.unwrap();
572
573 assert!(registry.contains("plugin-1").await);
574 assert!(!registry.contains("plugin-2").await);
575
576 let loaded = registry.get("plugin-1").await.unwrap();
577 assert_eq!(loaded.name, "Plugin 1");
578
579 registry.unregister("plugin-1").await.unwrap();
580 assert!(!registry.contains("plugin-1").await);
581 }
582
583 #[tokio::test]
584 async fn test_registry_load_order() {
585 let registry = PluginRegistry::new();
586
587 let a = PluginInfo::new("a", "A", PluginVersion::new(1, 0, 0));
589 registry.register(a).await.unwrap();
590
591 let b = PluginInfo::new("b", "B", PluginVersion::new(1, 0, 0)).with_dependency("a");
593 registry.register(b).await.unwrap();
594
595 let c = PluginInfo::new("c", "C", PluginVersion::new(1, 0, 0)).with_dependency("b");
597 registry.register(c).await.unwrap();
598
599 let order = registry.get_load_order().await.unwrap();
600
601 let pos_a = order.iter().position(|x| x == "a").unwrap();
603 let pos_b = order.iter().position(|x| x == "b").unwrap();
604 let pos_c = order.iter().position(|x| x == "c").unwrap();
605
606 assert!(pos_a < pos_b);
607 assert!(pos_b < pos_c);
608 }
609}