stynx_code_plugins/application/
lifecycle.rs1use std::collections::HashMap;
2
3use tokio::process::Child;
4
5use crate::domain::plugin::{PluginId, PluginInfo};
6use stynx_code_errors::{AppError, AppResult};
7
8pub struct PluginLifecycleManager {
9 processes: HashMap<PluginId, Child>,
10}
11
12impl PluginLifecycleManager {
13 pub fn new() -> Self {
14 Self {
15 processes: HashMap::new(),
16 }
17 }
18
19 pub async fn start(&mut self, info: &PluginInfo) -> AppResult<()> {
20 if self.processes.contains_key(&info.id) {
21 return Err(AppError::BadRequest(format!(
22 "Plugin '{}' is already running",
23 info.id
24 )));
25 }
26
27 let entry_point = info.path.join("run");
28 let child = tokio::process::Command::new(&entry_point)
29 .stdin(std::process::Stdio::piped())
30 .stdout(std::process::Stdio::piped())
31 .stderr(std::process::Stdio::null())
32 .spawn()
33 .map_err(|e| {
34 AppError::Internal(anyhow::anyhow!(
35 "Failed to spawn plugin '{}' at {}: {e}",
36 info.id,
37 entry_point.display()
38 ))
39 })?;
40
41 self.processes.insert(info.id.clone(), child);
42 Ok(())
43 }
44
45 pub async fn stop(&mut self, id: &PluginId) -> AppResult<()> {
46 let child = self.processes.get_mut(id).ok_or_else(|| {
47 AppError::BadRequest(format!("Plugin '{}' is not running", id))
48 })?;
49
50 child.kill().await.map_err(|e| {
51 AppError::Internal(anyhow::anyhow!("Failed to kill plugin '{}': {e}", id))
52 })?;
53
54 self.processes.remove(id);
55 Ok(())
56 }
57
58 pub fn health_check(&mut self, id: &PluginId) -> bool {
59 match self.processes.get_mut(id) {
60 None => false,
61 Some(child) => matches!(child.try_wait(), Ok(None)),
62 }
63 }
64}
65
66impl Default for PluginLifecycleManager {
67 fn default() -> Self {
68 Self::new()
69 }
70}