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 a web server (persistent)"
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>'".to_string(),
34 ));
35 }
36
37 let config = get_config()?;
38
39 let ctx = crate::server::shared::get_shared_context();
40 self.stop_server(&config, ctx, args[0])
41 }
42
43 fn priority(&self) -> u8 {
44 67
45 }
46}
47
48impl StopCommand {
49 fn stop_server(
50 &self,
51 config: &Config,
52 ctx: &ServerContext,
53 identifier: &str,
54 ) -> Result<String> {
55 let (server_info, handle) = {
57 let servers_guard = ctx
58 .servers
59 .read()
60 .map_err(|_| AppError::Validation("Server-Context lock poisoned".to_string()))?;
61
62 let server_info = find_server(&servers_guard, identifier)?.clone();
63
64 if server_info.status != ServerStatus::Running {
65 return Ok(format!(
66 "Server '{}' is not active (Status: {})",
67 server_info.name, server_info.status
68 ));
69 }
70
71 let handle = {
73 let mut handles_guard = ctx.handles.write().map_err(|_| {
74 AppError::Validation("Handle-Context lock poisoned".to_string())
75 })?;
76 handles_guard.remove(&server_info.id)
77 };
78
79 (server_info, handle)
80 };
81
82 log::info!(
83 "Stopping server {} on port {}",
84 server_info.id,
85 server_info.port
86 );
87
88 self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
90
91 self.notify_browser_shutdown(&server_info);
93
94 if let Some(handle) = handle {
95 self.shutdown_server_gracefully(handle, server_info.id.clone(), config);
97
98 let server_id = server_info.id.clone();
100 tokio::spawn(async move {
101 crate::server::shared::persist_server_update(&server_id, ServerStatus::Stopped)
102 .await;
103 });
104
105 std::thread::sleep(Duration::from_millis(
107 config.server.startup_delay_ms.min(500),
108 ));
109
110 let running_count = {
111 let servers = ctx.servers.read().unwrap_or_else(|e| {
112 log::warn!("Server lock poisoned: {}", e);
113 e.into_inner()
114 });
115 servers
116 .values()
117 .filter(|s| s.status == ServerStatus::Running)
118 .count()
119 };
120
121 Ok(format!(
122 "Server '{}' stopped [PERSISTENT] ({}/{} running)",
123 server_info.name, running_count, config.server.max_concurrent
124 ))
125 } else {
126 let server_id = server_info.id.clone();
128 tokio::spawn(async move {
129 crate::server::shared::persist_server_update(&server_id, ServerStatus::Stopped)
130 .await;
131 });
132
133 Ok(format!(
134 "Server '{}' was already stopped [PERSISTENT]",
135 server_info.name
136 ))
137 }
138 }
139
140 fn notify_browser_shutdown(&self, server_info: &crate::server::types::ServerInfo) {
141 let server_port = server_info.port;
142 let server_name = server_info.name.clone();
143
144 tokio::spawn(async move {
146 let server_url = format!("http://127.0.0.1:{}", server_port);
147 log::info!(
148 "Notifying browser to close for server {} (async)",
149 server_name
150 );
151
152 let client = reqwest::Client::new();
153 if let Err(e) = client
154 .get(format!("{}/api/close-browser", server_url)) .timeout(std::time::Duration::from_millis(300))
156 .send()
157 .await
158 {
159 log::warn!("Failed to notify browser: {}", e);
160 }
161
162 tokio::time::sleep(std::time::Duration::from_millis(500)).await;
164 });
165 }
166
167 fn shutdown_server_gracefully(
168 &self,
169 handle: actix_web::dev::ServerHandle,
170 server_id: String,
171 config: &Config,
172 ) {
173 let shutdown_timeout = config.server.shutdown_timeout;
174
175 tokio::spawn(async move {
176 use tokio::time::{timeout, Duration};
177
178 match timeout(Duration::from_secs(shutdown_timeout), handle.stop(true)).await {
179 Ok(_) => log::info!("Server {} stopped gracefully", server_id),
180 Err(_) => {
181 log::warn!(
182 "Server {} shutdown timeout ({}s), forcing stop",
183 server_id,
184 shutdown_timeout
185 );
186 handle.stop(false).await;
187 }
188 }
189 });
190 }
191
192 fn update_server_status(&self, ctx: &ServerContext, server_id: &str, status: ServerStatus) {
193 if let Ok(mut servers) = ctx.servers.write() {
194 if let Some(server) = servers.get_mut(server_id) {
195 server.status = status;
196 }
197 }
198 }
199}