rush_sync_server/commands/stop/
command.rs

1use crate::commands::command::Command;
2use crate::core::prelude::*;
3use crate::server::types::{ServerContext, ServerStatus};
4use crate::server::utils::validation::find_server;
5use std::future::Future;
6use std::pin::Pin;
7use std::time::Duration;
8use tokio::time::timeout;
9
10#[derive(Debug, Default)]
11pub struct StopCommand;
12
13impl StopCommand {
14    pub fn new() -> Self {
15        Self
16    }
17}
18
19impl Command for StopCommand {
20    fn name(&self) -> &'static str {
21        "stop"
22    }
23
24    fn description(&self) -> &'static str {
25        "Stop a web server"
26    }
27
28    fn matches(&self, command: &str) -> bool {
29        command.trim().to_lowercase().starts_with("stop")
30    }
31
32    fn execute_sync(&self, args: &[&str]) -> Result<String> {
33        if args.is_empty() {
34            return Err(AppError::Validation(
35                "Server-ID/Name fehlt! Verwende 'stop <ID>'".to_string(),
36            ));
37        }
38
39        let ctx = crate::server::shared::get_shared_context();
40        self.stop_server(ctx, args[0])
41    }
42
43    fn execute_async<'a>(
44        &'a self,
45        args: &'a [&'a str],
46    ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'a>> {
47        Box::pin(async move { self.execute_sync(args) })
48    }
49
50    fn supports_async(&self) -> bool {
51        true
52    }
53
54    fn priority(&self) -> u8 {
55        67
56    }
57}
58
59impl StopCommand {
60    fn stop_server(&self, ctx: &ServerContext, identifier: &str) -> Result<String> {
61        let server_info = {
62            let servers = ctx.servers.read().unwrap();
63            find_server(&servers, identifier)?.clone()
64        };
65
66        if server_info.status != ServerStatus::Running {
67            return Ok(format!(
68                "Server '{}' ist nicht aktiv (Status: {})",
69                server_info.name, server_info.status
70            ));
71        }
72
73        log::info!(
74            "Stopping server {} on port {}",
75            server_info.id,
76            server_info.port
77        );
78
79        let handle_removed = {
80            let mut handles = ctx.handles.write().unwrap();
81            handles.remove(&server_info.id)
82        };
83
84        if let Some(handle) = handle_removed {
85            self.shutdown_server_gracefully(handle, server_info.id.clone());
86            self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
87            std::thread::sleep(Duration::from_millis(1000));
88            Ok(format!("Server '{}' gestoppt", server_info.name))
89        } else {
90            self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
91            Ok(format!(
92                "Server '{}' war bereits gestoppt",
93                server_info.name
94            ))
95        }
96    }
97
98    fn shutdown_server_gracefully(&self, handle: actix_web::dev::ServerHandle, server_id: String) {
99        std::thread::spawn(move || {
100            let rt = tokio::runtime::Runtime::new().unwrap();
101            rt.block_on(async move {
102                match timeout(Duration::from_secs(3), handle.stop(true)).await {
103                    Ok(_) => log::info!("Server {} stopped gracefully", server_id),
104                    Err(_) => {
105                        log::warn!("Server {} shutdown timeout, forcing stop", server_id);
106                        handle.stop(false).await;
107                    }
108                }
109            });
110        });
111    }
112
113    fn update_server_status(&self, ctx: &ServerContext, server_id: &str, status: ServerStatus) {
114        if let Ok(mut servers) = ctx.servers.write() {
115            if let Some(server) = servers.get_mut(server_id) {
116                server.status = status;
117            }
118        }
119    }
120}