rush_sync_server/commands/cleanup/
command.rs1use 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 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 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 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 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 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 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}