rush_sync_server/commands/cleanup/
command.rs

1use crate::commands::command::Command;
2use crate::core::prelude::*;
3use crate::server::types::{ServerContext, ServerStatus};
4use std::future::Future;
5use std::pin::Pin;
6use std::time::Duration;
7use tokio::time::timeout;
8
9#[derive(Debug, Default)]
10pub struct CleanupCommand;
11
12impl CleanupCommand {
13    pub fn new() -> Self {
14        Self
15    }
16}
17
18impl Command for CleanupCommand {
19    fn name(&self) -> &'static str {
20        "cleanup"
21    }
22    fn description(&self) -> &'static str {
23        "Clean up stopped or failed servers (persistent)"
24    }
25    fn matches(&self, command: &str) -> bool {
26        command.trim().to_lowercase().starts_with("cleanup")
27    }
28
29    fn execute_sync(&self, args: &[&str]) -> Result<String> {
30        let ctx = crate::server::shared::get_shared_context();
31
32        match args.first() {
33            Some(&"failed") => Ok(self.cleanup_failed_servers(ctx)),
34            Some(&"stopped") | None => Ok(self.cleanup_stopped_servers(ctx)),
35            Some(&"logs") => {
36                // Jetzt ohne self - statischer Aufruf
37                tokio::spawn(async move {
38                    match Self::cleanup_all_server_logs().await {
39                        Ok(msg) => log::info!("Log cleanup result: {}", msg),
40                        Err(e) => log::error!("Log cleanup failed: {}", e),
41                    }
42                });
43                Ok("Server-Log-Bereinigung gestartet...".to_string())
44            }
45            Some(&"all") => {
46                let stopped = self.cleanup_stopped_servers(ctx);
47                let failed = self.cleanup_failed_servers(ctx);
48
49                tokio::spawn(async move {
50                    match Self::cleanup_all_server_logs().await {
51                        Ok(msg) => log::info!("Log cleanup result: {}", msg),
52                        Err(e) => log::error!("Log cleanup failed: {}", e),
53                    }
54                });
55
56                Ok(format!(
57                    "{}\n{}\nServer-Logs werden bereinigt...",
58                    stopped, failed
59                ))
60            }
61            _ => Err(AppError::Validation(
62                "Usage: cleanup [stopped|failed|logs|all]".to_string(),
63            )),
64        }
65    }
66
67    fn execute_async<'a>(
68        &'a self,
69        args: &'a [&'a str],
70    ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'a>> {
71        Box::pin(async move { self.execute_sync(args) })
72    }
73
74    fn supports_async(&self) -> bool {
75        true
76    }
77    fn priority(&self) -> u8 {
78        50
79    }
80}
81
82impl CleanupCommand {
83    fn cleanup_stopped_servers(&self, ctx: &ServerContext) -> String {
84        let registry = crate::server::shared::get_persistent_registry();
85
86        // Async cleanup mit Persistence
87        tokio::spawn(async move {
88            if let Ok(servers) = registry.load_servers().await {
89                if let Ok((_updated_servers, removed_count)) = registry
90                    .cleanup_servers(servers, crate::server::persistence::CleanupType::Stopped)
91                    .await
92                {
93                    if removed_count > 0 {
94                        log::info!(
95                            "Removed {} stopped servers from persistent registry",
96                            removed_count
97                        );
98                    }
99                }
100            }
101        });
102
103        // Sofortige Runtime-Cleanup
104        let mut servers = ctx.servers.write().unwrap();
105        let initial_count = servers.len();
106        servers.retain(|_, server| server.status != ServerStatus::Stopped);
107        let removed_count = initial_count - servers.len();
108
109        if removed_count > 0 {
110            format!(
111                "{} gestoppte Server entfernt (persistent gespeichert)",
112                removed_count
113            )
114        } else {
115            "Keine gestoppten Server zum Entfernen gefunden".to_string()
116        }
117    }
118
119    fn cleanup_failed_servers(&self, ctx: &ServerContext) -> String {
120        let registry = crate::server::shared::get_persistent_registry();
121
122        // Async cleanup mit Persistence
123        tokio::spawn(async move {
124            if let Ok(servers) = registry.load_servers().await {
125                if let Ok((_updated_servers, removed_count)) = registry
126                    .cleanup_servers(servers, crate::server::persistence::CleanupType::Failed)
127                    .await
128                {
129                    if removed_count > 0 {
130                        log::info!(
131                            "Removed {} failed servers from persistent registry",
132                            removed_count
133                        );
134                    }
135                }
136            }
137        });
138
139        // Sofortige Runtime-Cleanup
140        let mut servers = ctx.servers.write().unwrap();
141        let initial_count = servers.len();
142        servers.retain(|_, server| server.status != ServerStatus::Failed);
143        let removed_count = initial_count - servers.len();
144
145        if removed_count > 0 {
146            format!(
147                "{} fehlgeschlagene Server entfernt (persistent gespeichert)",
148                removed_count
149            )
150        } else {
151            "Keine fehlgeschlagenen Server zum Entfernen gefunden".to_string()
152        }
153    }
154
155    pub async fn shutdown_all_servers(&self, ctx: &ServerContext) -> Result<()> {
156        let handles: Vec<_> = {
157            let mut handles_guard = ctx.handles.write().unwrap();
158            handles_guard.drain().collect()
159        };
160
161        let shutdown_futures: Vec<_> = handles
162            .into_iter()
163            .map(|(id, handle)| async move {
164                match timeout(Duration::from_secs(5), handle.stop(true)).await {
165                    Ok(_) => log::info!("Server {} stopped", id),
166                    Err(_) => {
167                        log::warn!("Force stopping server {}", id);
168                        handle.stop(false).await;
169                    }
170                }
171            })
172            .collect();
173
174        futures::future::join_all(shutdown_futures).await;
175
176        {
177            let mut servers = ctx.servers.write().unwrap();
178            for server in servers.values_mut() {
179                server.status = ServerStatus::Stopped;
180            }
181        }
182
183        // Persistenz-Update für alle Server
184        let registry = crate::server::shared::get_persistent_registry();
185        if let Ok(mut persistent_servers) = registry.load_servers().await {
186            for server in persistent_servers.values_mut() {
187                server.status = ServerStatus::Stopped;
188            }
189            let _ = registry.save_servers(&persistent_servers).await;
190        }
191
192        Ok(())
193    }
194
195    pub async fn cleanup_all_server_logs() -> Result<String> {
196        let exe_path = std::env::current_exe().map_err(AppError::Io)?;
197        let base_dir = exe_path.parent().ok_or_else(|| {
198            AppError::Validation("Cannot determine executable directory".to_string())
199        })?;
200
201        let servers_dir = base_dir.join(".rss").join("servers");
202
203        if !servers_dir.exists() {
204            return Ok("Kein servers/ Verzeichnis gefunden".to_string());
205        }
206
207        let mut deleted_files = 0;
208        let mut total_size = 0u64;
209
210        let mut entries = tokio::fs::read_dir(&servers_dir)
211            .await
212            .map_err(AppError::Io)?;
213
214        while let Some(entry) = entries.next_entry().await.map_err(AppError::Io)? {
215            let path = entry.path();
216
217            if path.is_file() {
218                if let Some(extension) = path.extension() {
219                    if extension == "log" || extension == "gz" {
220                        if let Ok(metadata) = tokio::fs::metadata(&path).await {
221                            total_size += metadata.len();
222                        }
223
224                        tokio::fs::remove_file(&path).await.map_err(AppError::Io)?;
225                        deleted_files += 1;
226
227                        log::info!("Deleted log file: {}", path.display());
228                    }
229                }
230            }
231        }
232
233        let size_mb = total_size / (1024 * 1024);
234
235        Ok(format!(
236            "Server-Logs bereinigt: {} Dateien gelöscht, {}MB freigegeben",
237            deleted_files, size_mb
238        ))
239    }
240}