rush_sync_server/commands/stop/
command.rs1use crate::commands::command::Command;
3use crate::core::prelude::*;
4use crate::server::types::{ServerContext, ServerStatus};
5use crate::server::utils::validation::find_server;
6use std::time::Duration;
7
8#[derive(Debug, Default)]
9pub struct StopCommand;
10
11impl StopCommand {
12 pub fn new() -> Self {
13 Self
14 }
15}
16
17impl Command for StopCommand {
18 fn name(&self) -> &'static str {
19 "stop"
20 }
21
22 fn description(&self) -> &'static str {
23 "Stop server(s) - supports ranges and bulk operations"
24 }
25
26 fn matches(&self, command: &str) -> bool {
27 command.trim().to_lowercase().starts_with("stop")
28 }
29
30 fn execute_sync(&self, args: &[&str]) -> Result<String> {
31 if args.is_empty() {
32 return Err(AppError::Validation(
33 "Server-ID/Name fehlt! Verwende 'stop <ID>', 'stop 1-3', 'stop all'".to_string(),
34 ));
35 }
36
37 let config = get_config()?;
38 let ctx = crate::server::shared::get_shared_context();
39
40 match self.parse_stop_args(args) {
41 StopMode::Single(identifier) => self.stop_single_server(&config, ctx, &identifier),
42 StopMode::Range(start, end) => self.stop_range_servers(&config, ctx, start, end),
43 StopMode::All => self.stop_all_servers(&config, ctx),
44 StopMode::Invalid(error) => Err(AppError::Validation(error)),
45 }
46 }
47
48 fn priority(&self) -> u8 {
49 67
50 }
51}
52
53#[derive(Debug)]
54enum StopMode {
55 Single(String),
56 Range(u32, u32),
57 All,
58 Invalid(String),
59}
60
61impl StopCommand {
62 fn parse_stop_args(&self, args: &[&str]) -> StopMode {
64 if args.len() != 1 {
65 return StopMode::Invalid("Too many arguments".to_string());
66 }
67
68 let arg = args[0];
69
70 if arg.eq_ignore_ascii_case("all") {
72 return StopMode::All;
73 }
74
75 if let Some((start_str, end_str)) = arg.split_once('-') {
77 match (start_str.parse::<u32>(), end_str.parse::<u32>()) {
78 (Ok(start), Ok(end)) => {
79 if start == 0 || end == 0 {
80 return StopMode::Invalid("Range indices must be > 0".to_string());
81 }
82 if start > end {
83 return StopMode::Invalid("Start must be <= end in range".to_string());
84 }
85 if end - start > 20 {
86 return StopMode::Invalid(
87 "Maximum 20 servers in range operation".to_string(),
88 );
89 }
90 StopMode::Range(start, end)
91 }
92 _ => StopMode::Single(arg.to_string()),
93 }
94 } else {
95 StopMode::Single(arg.to_string())
97 }
98 }
99
100 fn stop_single_server(
102 &self,
103 config: &Config,
104 ctx: &ServerContext,
105 identifier: &str,
106 ) -> Result<String> {
107 let (server_info, handle) = {
108 let servers_guard = ctx
109 .servers
110 .read()
111 .map_err(|_| AppError::Validation("Server-Context lock poisoned".to_string()))?;
112
113 let server_info = find_server(&servers_guard, identifier)?.clone();
114
115 if server_info.status != ServerStatus::Running {
116 return Ok(format!(
117 "Server '{}' is not active (Status: {})",
118 server_info.name, server_info.status
119 ));
120 }
121
122 let handle = {
124 let mut handles_guard = ctx.handles.write().map_err(|_| {
125 AppError::Validation("Handle-Context lock poisoned".to_string())
126 })?;
127 handles_guard.remove(&server_info.id)
128 };
129
130 (server_info, handle)
131 };
132
133 log::info!(
134 "Stopping server {} on port {}",
135 server_info.id,
136 server_info.port
137 );
138
139 self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
141
142 self.notify_browser_shutdown(&server_info);
144
145 if let Some(handle) = handle {
146 self.shutdown_server_gracefully(handle, server_info.id.clone(), config);
148
149 let server_id = server_info.id.clone();
151 tokio::spawn(async move {
152 crate::server::shared::persist_server_update(&server_id, ServerStatus::Stopped)
153 .await;
154 });
155
156 std::thread::sleep(Duration::from_millis(
158 config.server.startup_delay_ms.min(500),
159 ));
160
161 let running_count = {
162 let servers = ctx.servers.read().unwrap_or_else(|e| {
163 log::warn!("Server lock poisoned: {}", e);
164 e.into_inner()
165 });
166 servers
167 .values()
168 .filter(|s| s.status == ServerStatus::Running)
169 .count()
170 };
171
172 Ok(format!(
173 "Server '{}' stopped [PERSISTENT] ({}/{} running)",
174 server_info.name, running_count, config.server.max_concurrent
175 ))
176 } else {
177 let server_id = server_info.id.clone();
179 tokio::spawn(async move {
180 crate::server::shared::persist_server_update(&server_id, ServerStatus::Stopped)
181 .await;
182 });
183
184 Ok(format!(
185 "Server '{}' was already stopped [PERSISTENT]",
186 server_info.name
187 ))
188 }
189 }
190
191 fn stop_range_servers(
193 &self,
194 config: &Config,
195 ctx: &ServerContext,
196 start: u32,
197 end: u32,
198 ) -> Result<String> {
199 let mut results = Vec::new();
200 let mut stopped_count = 0;
201 let mut failed_count = 0;
202
203 for i in start..=end {
204 let identifier = format!("{}", i);
205
206 match self.stop_single_server(config, ctx, &identifier) {
207 Ok(message) => {
208 if message.contains("stopped [PERSISTENT]") {
209 stopped_count += 1;
210 results.push(format!("Server {}: Stopped", i));
211 } else {
212 results.push(format!("Server {}: {}", i, message));
213 }
214 }
215 Err(e) => {
216 failed_count += 1;
217 results.push(format!("Server {}: Failed - {}", i, e));
218 }
219 }
220 }
221
222 let summary = format!(
223 "Range stop completed: {} stopped, {} failed (Range: {}-{})",
224 stopped_count, failed_count, start, end
225 );
226
227 if results.is_empty() {
228 Ok(summary)
229 } else {
230 Ok(format!("{}\n\nResults:\n{}", summary, results.join("\n")))
231 }
232 }
233
234 fn stop_all_servers(&self, config: &Config, ctx: &ServerContext) -> Result<String> {
236 let running_servers: Vec<_> = {
237 let servers = ctx.servers.read().unwrap();
238 servers
239 .values()
240 .filter(|s| s.status == ServerStatus::Running)
241 .map(|s| (s.id.clone(), s.name.clone()))
242 .collect()
243 };
244
245 if running_servers.is_empty() {
246 return Ok("No running servers to stop".to_string());
247 }
248
249 if running_servers.len() > 20 {
250 return Err(AppError::Validation(
251 "Too many servers to stop at once (max 20). Use ranges instead.".to_string(),
252 ));
253 }
254
255 let mut results = Vec::new();
256 let mut stopped_count = 0;
257 let mut failed_count = 0;
258
259 let server_stops: Vec<_> = running_servers
261 .into_iter()
262 .map(|(server_id, server_name)| {
263 match self.stop_single_server(config, ctx, &server_id) {
264 Ok(message) => {
265 if message.contains("stopped [PERSISTENT]") {
266 stopped_count += 1;
267 (server_name, "Stopped".to_string())
268 } else {
269 (server_name, message)
270 }
271 }
272 Err(e) => {
273 failed_count += 1;
274 (server_name, format!("Failed - {}", e))
275 }
276 }
277 })
278 .collect();
279
280 for (server_name, result) in server_stops {
281 results.push(format!("{}: {}", server_name, result));
282 }
283
284 let summary = format!(
285 "Stop all completed: {} stopped, {} failed",
286 stopped_count, failed_count
287 );
288
289 Ok(format!("{}\n\nResults:\n{}", summary, results.join("\n")))
290 }
291
292 fn notify_browser_shutdown(&self, server_info: &crate::server::types::ServerInfo) {
294 let server_port = server_info.port;
295 let server_name = server_info.name.clone();
296
297 tokio::spawn(async move {
298 let server_url = format!("http://127.0.0.1:{}", server_port);
299 log::info!(
300 "Notifying browser to close for server {} (async)",
301 server_name
302 );
303
304 let client = reqwest::Client::new();
305 if let Err(e) = client
306 .get(format!("{}/api/close-browser", server_url))
307 .timeout(std::time::Duration::from_millis(300))
308 .send()
309 .await
310 {
311 log::warn!("Failed to notify browser: {}", e);
312 }
313
314 tokio::time::sleep(std::time::Duration::from_millis(500)).await;
315 });
316 }
317
318 fn shutdown_server_gracefully(
320 &self,
321 handle: actix_web::dev::ServerHandle,
322 server_id: String,
323 config: &Config,
324 ) {
325 let shutdown_timeout = config.server.shutdown_timeout;
326
327 tokio::spawn(async move {
328 use tokio::time::{timeout, Duration};
329
330 match timeout(Duration::from_secs(shutdown_timeout), handle.stop(true)).await {
331 Ok(_) => log::info!("Server {} stopped gracefully", server_id),
332 Err(_) => {
333 log::warn!(
334 "Server {} shutdown timeout ({}s), forcing stop",
335 server_id,
336 shutdown_timeout
337 );
338 handle.stop(false).await;
339 }
340 }
341 });
342 }
343
344 fn update_server_status(&self, ctx: &ServerContext, server_id: &str, status: ServerStatus) {
346 if let Ok(mut servers) = ctx.servers.write() {
347 if let Some(server) = servers.get_mut(server_id) {
348 server.status = status;
349 }
350 }
351 }
352}