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::future::Future;
7use std::pin::Pin;
8use std::time::Duration;
9use tokio::time::timeout;
10
11#[derive(Debug, Default)]
12pub struct StopCommand;
13
14impl StopCommand {
15 pub fn new() -> Self {
16 Self
17 }
18}
19
20impl Command for StopCommand {
21 fn name(&self) -> &'static str {
22 "stop"
23 }
24
25 fn description(&self) -> &'static str {
26 "Stop a web server (persistent)"
27 }
28
29 fn matches(&self, command: &str) -> bool {
30 command.trim().to_lowercase().starts_with("stop")
31 }
32
33 fn execute_sync(&self, args: &[&str]) -> Result<String> {
34 if args.is_empty() {
35 return Err(AppError::Validation(
36 "Server-ID/Name fehlt! Verwende 'stop <ID>'".to_string(),
37 ));
38 }
39
40 let config_result = std::thread::spawn(|| {
42 let rt = tokio::runtime::Runtime::new().unwrap();
43 rt.block_on(Config::load())
44 })
45 .join()
46 .map_err(|_| AppError::Validation("Failed to load config".to_string()))??;
47
48 let ctx = crate::server::shared::get_shared_context();
49
50 self.stop_server(&config_result, ctx, args[0])
51 }
52
53 fn execute_async<'a>(
54 &'a self,
55 args: &'a [&'a str],
56 ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'a>> {
57 Box::pin(async move {
58 if args.is_empty() {
59 return Err(AppError::Validation(
60 "Server-ID/Name fehlt! Verwende 'stop <ID>'".to_string(),
61 ));
62 }
63
64 let config = Config::load().await?;
65 let ctx = crate::server::shared::get_shared_context();
66
67 self.stop_server_async(&config, ctx, args[0]).await
68 })
69 }
70
71 fn supports_async(&self) -> bool {
72 true
73 }
74
75 fn priority(&self) -> u8 {
76 67
77 }
78}
79
80impl StopCommand {
81 fn stop_server(
82 &self,
83 config: &Config,
84 ctx: &ServerContext,
85 identifier: &str,
86 ) -> Result<String> {
87 let server_info = {
88 let servers = ctx.servers.read().unwrap();
89 find_server(&servers, identifier)?.clone()
90 };
91
92 if server_info.status != ServerStatus::Running {
93 return Ok(format!(
94 "Server '{}' is not active (Status: {})",
95 server_info.name, server_info.status
96 ));
97 }
98
99 log::info!(
100 "Stopping server {} on port {}",
101 server_info.id,
102 server_info.port
103 );
104
105 let handle_removed = {
106 let mut handles = ctx.handles.write().unwrap();
107 handles.remove(&server_info.id)
108 };
109
110 if let Some(handle) = handle_removed {
111 self.shutdown_server_gracefully(handle, server_info.id.clone(), config);
112 self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
113
114 let server_id = server_info.id.clone();
116 tokio::spawn(async move {
117 crate::server::shared::persist_server_update(&server_id, ServerStatus::Stopped)
118 .await;
119 });
120
121 std::thread::sleep(Duration::from_millis(config.server.startup_delay_ms));
123
124 let running_count = {
125 let servers = ctx.servers.read().unwrap();
126 servers
127 .values()
128 .filter(|s| s.status == ServerStatus::Running)
129 .count()
130 };
131
132 Ok(format!(
133 "Server '{}' stopped [PERSISTENT] ({}/{} running)",
134 server_info.name, running_count, config.server.max_concurrent
135 ))
136 } else {
137 self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
138
139 let server_id = server_info.id.clone();
141 tokio::spawn(async move {
142 crate::server::shared::persist_server_update(&server_id, ServerStatus::Stopped)
143 .await;
144 });
145
146 Ok(format!(
147 "Server '{}' was already stopped [PERSISTENT]",
148 server_info.name
149 ))
150 }
151 }
152
153 async fn stop_server_async(
154 &self,
155 config: &Config,
156 ctx: &ServerContext,
157 identifier: &str,
158 ) -> Result<String> {
159 let server_info = {
160 let servers = ctx.servers.read().unwrap();
161 find_server(&servers, identifier)?.clone()
162 };
163
164 if server_info.status != ServerStatus::Running {
165 return Ok(format!(
166 "Server '{}' is not active (Status: {})",
167 server_info.name, server_info.status
168 ));
169 }
170
171 log::info!(
172 "Stopping server {} on port {} (async)",
173 server_info.id,
174 server_info.port
175 );
176
177 let handle_removed = {
178 let mut handles = ctx.handles.write().unwrap();
179 handles.remove(&server_info.id)
180 };
181
182 if let Some(handle) = handle_removed {
183 let shutdown_timeout_duration = Duration::from_secs(config.server.shutdown_timeout);
185
186 match timeout(shutdown_timeout_duration, handle.stop(true)).await {
187 Ok(_) => log::info!("Server {} stopped gracefully", server_info.id),
188 Err(_) => {
189 log::warn!("Server {} shutdown timeout, forcing stop", server_info.id);
190 handle.stop(false).await;
191 }
192 }
193
194 self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
195
196 let server_id = server_info.id.clone();
198 tokio::spawn(async move {
199 crate::server::shared::persist_server_update(&server_id, ServerStatus::Stopped)
200 .await;
201 });
202
203 let running_count = {
204 let servers = ctx.servers.read().unwrap();
205 servers
206 .values()
207 .filter(|s| s.status == ServerStatus::Running)
208 .count()
209 };
210
211 Ok(format!(
212 "Server '{}' stopped [PERSISTENT] ({}/{} running)",
213 server_info.name, running_count, config.server.max_concurrent
214 ))
215 } else {
216 self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
217
218 let server_id = server_info.id.clone();
220 tokio::spawn(async move {
221 crate::server::shared::persist_server_update(&server_id, ServerStatus::Stopped)
222 .await;
223 });
224
225 Ok(format!(
226 "Server '{}' was already stopped [PERSISTENT]",
227 server_info.name
228 ))
229 }
230 }
231
232 fn shutdown_server_gracefully(
234 &self,
235 handle: actix_web::dev::ServerHandle,
236 server_id: String,
237 config: &Config,
238 ) {
239 let shutdown_timeout = config.server.shutdown_timeout;
240
241 std::thread::spawn(move || {
242 let rt = tokio::runtime::Runtime::new().unwrap();
243 rt.block_on(async move {
244 match timeout(Duration::from_secs(shutdown_timeout), handle.stop(true)).await {
245 Ok(_) => log::info!("Server {} stopped gracefully", server_id),
246 Err(_) => {
247 log::warn!(
248 "Server {} shutdown timeout ({}s), forcing stop",
249 server_id,
250 shutdown_timeout
251 );
252 handle.stop(false).await;
253 }
254 }
255 });
256 });
257 }
258
259 fn update_server_status(&self, ctx: &ServerContext, server_id: &str, status: ServerStatus) {
260 if let Ok(mut servers) = ctx.servers.write() {
261 if let Some(server) = servers.get_mut(server_id) {
262 server.status = status;
263 }
264 }
265 }
266}