Skip to main content

synaps_cli/extensions/
loader.rs

1//! Async extension loading orchestration.
2//!
3//! The chat UI owns the manager behind an async lock; this module keeps startup
4//! snappy by running discovery/loading in the background and streaming progress
5//! events back to the UI (which can render them as toasts).
6
7use std::sync::Arc;
8
9use tokio::sync::{mpsc, RwLock};
10
11use super::manager::{ExtensionLoadFailure, ExtensionManager};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum ExtensionLoaderEvent {
15    Started,
16    Loaded { plugin: String, loaded: usize, failed: usize },
17    Failed { failure: ExtensionLoadFailure, loaded: usize, failed: usize },
18    Finished { loaded: Vec<String>, failed: Vec<ExtensionLoadFailure> },
19}
20
21impl ExtensionLoaderEvent {
22    pub fn progress_counts(&self) -> Option<(usize, usize)> {
23        match self {
24            ExtensionLoaderEvent::Started => Some((0, 0)),
25            ExtensionLoaderEvent::Loaded { loaded, failed, .. } => Some((*loaded, *failed)),
26            ExtensionLoaderEvent::Failed { loaded, failed, .. } => Some((*loaded, *failed)),
27            ExtensionLoaderEvent::Finished { loaded, failed } => Some((loaded.len(), failed.len())),
28        }
29    }
30}
31
32pub fn spawn_discover_and_load(
33    manager: Arc<RwLock<ExtensionManager>>,
34    tx: mpsc::UnboundedSender<ExtensionLoaderEvent>,
35) -> tokio::task::JoinHandle<()> {
36    tokio::spawn(async move {
37        let _ = tx.send(ExtensionLoaderEvent::Started);
38        let (loaded, failed) = manager.write().await.discover_and_load_with_progress(|event| {
39            let _ = tx.send(event);
40        }).await;
41        let _ = tx.send(ExtensionLoaderEvent::Finished { loaded, failed });
42    })
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn progress_counts_report_finished_totals() {
51        let event = ExtensionLoaderEvent::Finished {
52            loaded: vec!["a".into(), "b".into()],
53            failed: vec![ExtensionLoadFailure {
54                plugin: "bad".into(),
55                manifest_path: None,
56                reason: "oops".into(),
57                hint: "fix it".into(),
58            }],
59        };
60        assert_eq!(event.progress_counts(), Some((2, 1)));
61    }
62}