rush_sync_server/commands/cleanup/
command.rs

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