rush_sync_server/commands/cleanup/
command.rs

1// CleanupCommand - src/commands/cleanup/command.rs
2use crate::commands::command::Command;
3use crate::core::prelude::*;
4use crate::server::types::{ServerContext, ServerStatus};
5use std::future::Future;
6use std::pin::Pin;
7use std::time::Duration;
8use tokio::time::timeout;
9
10#[derive(Debug, Default)]
11pub struct CleanupCommand;
12
13impl CleanupCommand {
14    pub fn new() -> Self {
15        Self
16    }
17}
18
19impl Command for CleanupCommand {
20    fn name(&self) -> &'static str {
21        "cleanup"
22    }
23    fn description(&self) -> &'static str {
24        "Clean up stopped or failed servers"
25    }
26    fn matches(&self, command: &str) -> bool {
27        command.trim().to_lowercase().starts_with("cleanup")
28    }
29
30    fn execute_sync(&self, args: &[&str]) -> Result<String> {
31        let ctx = crate::server::shared::get_shared_context();
32
33        match args.first() {
34            Some(&"failed") => Ok(self.cleanup_failed_servers(ctx)),
35            Some(&"stopped") | None => Ok(self.cleanup_stopped_servers(ctx)),
36            Some(&"all") => {
37                let stopped = self.cleanup_stopped_servers(ctx);
38                let failed = self.cleanup_failed_servers(ctx);
39                Ok(format!("{}\n{}", stopped, failed))
40            }
41            _ => Err(AppError::Validation(
42                "Usage: cleanup [stopped|failed|all]".to_string(),
43            )),
44        }
45    }
46
47    fn execute_async<'a>(
48        &'a self,
49        args: &'a [&'a str],
50    ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'a>> {
51        Box::pin(async move { self.execute_sync(args) })
52    }
53
54    fn supports_async(&self) -> bool {
55        true
56    }
57    fn priority(&self) -> u8 {
58        50
59    }
60}
61
62impl CleanupCommand {
63    fn cleanup_stopped_servers(&self, ctx: &ServerContext) -> String {
64        let mut servers = ctx.servers.write().unwrap();
65        let initial_count = servers.len();
66        servers.retain(|_, server| server.status != ServerStatus::Stopped);
67        let removed_count = initial_count - servers.len();
68
69        if removed_count > 0 {
70            format!("{} gestoppte Server entfernt", removed_count)
71        } else {
72            "Keine gestoppten Server zum Entfernen gefunden".to_string()
73        }
74    }
75
76    fn cleanup_failed_servers(&self, ctx: &ServerContext) -> String {
77        let mut servers = ctx.servers.write().unwrap();
78        let initial_count = servers.len();
79        servers.retain(|_, server| server.status != ServerStatus::Failed);
80        let removed_count = initial_count - servers.len();
81
82        if removed_count > 0 {
83            format!("{} fehlgeschlagene Server entfernt", removed_count)
84        } else {
85            "Keine fehlgeschlagenen Server zum Entfernen gefunden".to_string()
86        }
87    }
88
89    pub async fn shutdown_all_servers(&self, ctx: &ServerContext) -> Result<()> {
90        let handles: Vec<_> = {
91            let mut handles_guard = ctx.handles.write().unwrap();
92            handles_guard.drain().collect()
93        };
94
95        let shutdown_futures: Vec<_> = handles
96            .into_iter()
97            .map(|(id, handle)| async move {
98                match timeout(Duration::from_secs(5), handle.stop(true)).await {
99                    Ok(_) => log::info!("Server {} stopped", id),
100                    Err(_) => {
101                        log::warn!("Force stopping server {}", id);
102                        handle.stop(false).await;
103                    }
104                }
105            })
106            .collect();
107
108        futures::future::join_all(shutdown_futures).await;
109
110        {
111            let mut servers = ctx.servers.write().unwrap();
112            for server in servers.values_mut() {
113                server.status = ServerStatus::Stopped;
114            }
115        }
116        Ok(())
117    }
118}