rush_sync_server/commands/start/
command.rs1use crate::commands::command::Command;
3use crate::core::prelude::*;
4use crate::server::types::{ServerContext, ServerStatus};
5use crate::server::utils::port::is_port_available;
6use crate::server::utils::validation::find_server;
7use std::future::Future;
8use std::pin::Pin;
9
10#[derive(Debug, Default)]
11pub struct StartCommand;
12
13impl StartCommand {
14 pub fn new() -> Self {
15 Self
16 }
17}
18
19impl Command for StartCommand {
20 fn name(&self) -> &'static str {
21 "start"
22 }
23 fn description(&self) -> &'static str {
24 "Start a web server (persistent)"
25 }
26 fn matches(&self, command: &str) -> bool {
27 command.trim().to_lowercase().starts_with("start")
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 'start <ID>'".to_string(),
34 ));
35 }
36
37 let config_result = std::thread::spawn(|| {
39 let rt = tokio::runtime::Runtime::new().unwrap();
40 rt.block_on(Config::load())
41 })
42 .join()
43 .map_err(|_| AppError::Validation("Failed to load config".to_string()))??;
44
45 let ctx = crate::server::shared::get_shared_context();
46
47 self.start_server_sync(&config_result, ctx, args[0])
48 }
49
50 fn execute_async<'a>(
51 &'a self,
52 args: &'a [&'a str],
53 ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'a>> {
54 Box::pin(async move {
55 if args.is_empty() {
56 return Err(AppError::Validation(
57 "Server-ID/Name fehlt! Verwende 'start <ID>'".to_string(),
58 ));
59 }
60
61 let config = Config::load().await?;
62 let ctx = crate::server::shared::get_shared_context();
63
64 self.start_server_async(&config, ctx, args[0]).await
65 })
66 }
67
68 fn supports_async(&self) -> bool {
69 true
70 }
71 fn priority(&self) -> u8 {
72 66
73 }
74}
75
76impl StartCommand {
77 fn start_server_sync(
78 &self,
79 config: &Config,
80 ctx: &ServerContext,
81 identifier: &str,
82 ) -> Result<String> {
83 let server_info = {
84 let servers = ctx.servers.read().unwrap();
85 find_server(&servers, identifier)?.clone()
86 };
87
88 self.validate_and_start_server(config, ctx, server_info)
89 }
90
91 async fn start_server_async(
92 &self,
93 config: &Config,
94 ctx: &ServerContext,
95 identifier: &str,
96 ) -> Result<String> {
97 let server_info = {
98 let servers = ctx.servers.read().unwrap();
99 find_server(&servers, identifier)?.clone()
100 };
101
102 self.validate_and_start_server(config, ctx, server_info)
103 }
104
105 fn validate_and_start_server(
106 &self,
107 config: &Config,
108 ctx: &ServerContext,
109 server_info: crate::server::types::ServerInfo,
110 ) -> Result<String> {
111 if server_info.status == ServerStatus::Running {
113 if !is_port_available(server_info.port) {
114 self.update_server_status(ctx, &server_info.id, ServerStatus::Failed);
116
117 let server_id = server_info.id.clone();
119 tokio::spawn(async move {
120 crate::server::shared::persist_server_update(&server_id, ServerStatus::Failed)
121 .await;
122 });
123
124 return Ok(format!(
125 "Port {} is occupied by another process! Server status corrected to FAILED. Use different port.",
126 server_info.port
127 ));
128 } else {
129 self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
131
132 let server_id = server_info.id.clone();
133 tokio::spawn(async move {
134 crate::server::shared::persist_server_update(&server_id, ServerStatus::Stopped)
135 .await;
136 });
137
138 log::info!(
139 "Corrected server status from Running to Stopped for {}",
140 server_info.name
141 );
142 }
143 }
144
145 if !is_port_available(server_info.port) {
147 return Ok(format!("Port {} already occupied!", server_info.port));
148 }
149
150 let running_servers = {
152 let servers = ctx.servers.read().unwrap();
153 servers
154 .values()
155 .filter(|s| s.status == ServerStatus::Running)
156 .count()
157 };
158
159 if running_servers >= config.server.max_concurrent {
160 return Err(AppError::Validation(format!(
161 "Cannot start server: Running servers limit reached ({}/{}). Stop other servers first or increase max_concurrent in config.",
162 running_servers, config.server.max_concurrent
163 )));
164 }
165
166 if server_info.port < config.server.port_range_start
168 || server_info.port > config.server.port_range_end
169 {
170 log::warn!(
171 "Server {} port {} is outside configured range {}-{}, but starting anyway",
172 server_info.name,
173 server_info.port,
174 config.server.port_range_start,
175 config.server.port_range_end
176 );
177 }
178
179 match self.spawn_server(config, ctx, server_info.clone()) {
181 Ok(handle) => {
182 {
183 let mut handles = ctx.handles.write().unwrap();
184 handles.insert(server_info.id.clone(), handle);
185 }
186 self.update_server_status(ctx, &server_info.id, ServerStatus::Running);
187
188 let server_id = server_info.id.clone();
190 tokio::spawn(async move {
191 crate::server::shared::persist_server_update(&server_id, ServerStatus::Running)
192 .await;
193 });
194
195 Ok(format!(
196 "Server '{}' successfully started on http://127.0.0.1:{} [PERSISTENT] ({}/{} running)",
197 server_info.name,
198 server_info.port,
199 running_servers + 1,
200 config.server.max_concurrent
201 ))
202 }
203 Err(e) => {
204 self.update_server_status(ctx, &server_info.id, ServerStatus::Failed);
205
206 let server_id = server_info.id.clone();
208 tokio::spawn(async move {
209 crate::server::shared::persist_server_update(&server_id, ServerStatus::Failed)
210 .await;
211 });
212
213 Err(AppError::Validation(format!("Server start failed: {}", e)))
214 }
215 }
216 }
217
218 fn spawn_server(
220 &self,
221 config: &Config,
222 ctx: &ServerContext,
223 server_info: crate::server::types::ServerInfo,
224 ) -> std::result::Result<actix_web::dev::ServerHandle, String> {
225 crate::server::handlers::web::create_web_server(ctx, server_info, config)
227 }
228
229 fn update_server_status(&self, ctx: &ServerContext, server_id: &str, status: ServerStatus) {
230 if let Ok(mut servers) = ctx.servers.write() {
231 if let Some(server) = servers.get_mut(server_id) {
232 server.status = status;
233 }
234 }
235 }
236}