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 opener;
8
9#[derive(Debug, Default)]
10pub struct StartCommand;
11
12impl StartCommand {
13 pub fn new() -> Self {
14 Self
15 }
16}
17
18impl Command for StartCommand {
19 fn name(&self) -> &'static str {
20 "start"
21 }
22 fn description(&self) -> &'static str {
23 "Start a web server (persistent)"
24 }
25 fn matches(&self, command: &str) -> bool {
26 command.trim().to_lowercase().starts_with("start")
27 }
28
29 fn execute_sync(&self, args: &[&str]) -> Result<String> {
30 if args.is_empty() {
31 return Err(AppError::Validation(
32 "Server-ID/Name fehlt! Verwende 'start <ID>'".to_string(),
33 ));
34 }
35
36 let _server_id = args[0];
37 let _browser_override = StartCommand::parse_browser_override(&args[1..]);
38
39 let config = get_config()?;
40 let ctx = crate::server::shared::get_shared_context();
41
42 self.start_server_sync(&config, ctx, args[0])
44 }
45
46 fn priority(&self) -> u8 {
47 66
48 }
49}
50
51impl StartCommand {
52 fn start_server_sync(
53 &self,
54 config: &Config,
55 ctx: &ServerContext,
56 identifier: &str,
57 ) -> Result<String> {
58 let server_info = {
59 let servers = ctx.servers.read().unwrap();
60 find_server(&servers, identifier)?.clone()
61 };
62
63 self.validate_and_start_server(config, ctx, server_info)
64 }
65
66 fn validate_and_start_server(
67 &self,
68 config: &Config,
69 ctx: &ServerContext,
70 server_info: crate::server::types::ServerInfo,
71 ) -> Result<String> {
72 if server_info.status == ServerStatus::Running {
74 if !is_port_available(server_info.port) {
75 self.update_server_status(ctx, &server_info.id, ServerStatus::Failed);
77
78 let server_id = server_info.id.clone();
80 tokio::spawn(async move {
81 crate::server::shared::persist_server_update(&server_id, ServerStatus::Failed)
82 .await;
83 });
84
85 return Ok(format!(
86 "Port {} is occupied by another process! Server status corrected to FAILED. Use different port.",
87 server_info.port
88 ));
89 } else {
90 self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
92
93 let server_id = server_info.id.clone();
94 tokio::spawn(async move {
95 crate::server::shared::persist_server_update(&server_id, ServerStatus::Stopped)
96 .await;
97 });
98
99 log::info!(
100 "Corrected server status from Running to Stopped for {}",
101 server_info.name
102 );
103 }
104 }
105
106 match crate::server::utils::port::check_port_status(server_info.port) {
109 crate::server::utils::port::PortStatus::Available => {
110 }
112 crate::server::utils::port::PortStatus::OccupiedByUs => {
113 return Ok(format!(
115 "Port {} wird bereits von Server '{}' verwendet!",
116 server_info.port, server_info.name
117 ));
118 }
119 crate::server::utils::port::PortStatus::OccupiedByOther => {
120 return Ok(format!(
122 "Port {} ist von anderem Prozess belegt! Verwende anderen Port.",
123 server_info.port
124 ));
125 }
126 }
127
128 let running_servers = {
130 let servers = ctx.servers.read().unwrap();
131 servers
132 .values()
133 .filter(|s| s.status == ServerStatus::Running)
134 .count()
135 };
136
137 if running_servers >= config.server.max_concurrent {
138 return Err(AppError::Validation(format!(
139 "Cannot start server: Running servers limit reached ({}/{}). Stop other servers first or increase max_concurrent in config.",
140 running_servers, config.server.max_concurrent
141 )));
142 }
143
144 if server_info.port < config.server.port_range_start
146 || server_info.port > config.server.port_range_end
147 {
148 log::warn!(
149 "Server {} port {} is outside configured range {}-{}, but starting anyway",
150 server_info.name,
151 server_info.port,
152 config.server.port_range_start,
153 config.server.port_range_end
154 );
155 }
156
157 match self.spawn_server(config, ctx, server_info.clone()) {
159 Ok(handle) => {
160 {
161 let mut handles = ctx.handles.write().unwrap();
162 handles.insert(server_info.id.clone(), handle);
163 }
164 self.update_server_status(ctx, &server_info.id, ServerStatus::Running);
165
166 let server_id = server_info.id.clone();
168 tokio::spawn(async move {
169 crate::server::shared::persist_server_update(&server_id, ServerStatus::Running)
170 .await;
171 });
172
173 let server_url = format!("http://127.0.0.1:{}", server_info.port);
174 if config.server.auto_open_browser {
175 let url_clone = server_url.clone();
176 let server_name_clone = server_info.name.clone();
177 tokio::spawn(async move {
178 tokio::time::sleep(tokio::time::Duration::from_millis(1200)).await;
179 match opener::open(&url_clone) {
180 Ok(_) => {
181 log::info!(
182 "Browser opened for '{}': {}",
183 server_name_clone,
184 url_clone
185 );
186 }
187 Err(e) => {
188 log::warn!(
189 "Failed to open browser for '{}': {} (URL: {})",
190 server_name_clone,
191 e,
192 url_clone
193 );
194 }
195 }
196 });
197 }
198
199 Ok(format!(
200 "Server '{}' successfully started on {} [PERSISTENT] ({}/{} running){}",
201 server_info.name,
202 server_url, running_servers + 1,
204 config.server.max_concurrent,
205 if config.server.auto_open_browser {
206 " - Browser opening..."
207 } else {
208 ""
209 }
210 ))
211 }
212 Err(e) => {
213 self.update_server_status(ctx, &server_info.id, ServerStatus::Failed);
214
215 let server_id = server_info.id.clone();
217 tokio::spawn(async move {
218 crate::server::shared::persist_server_update(&server_id, ServerStatus::Failed)
219 .await;
220 });
221
222 Err(AppError::Validation(format!("Server start failed: {}", e)))
223 }
224 }
225 }
226
227 fn spawn_server(
229 &self,
230 config: &Config,
231 ctx: &ServerContext,
232 server_info: crate::server::types::ServerInfo,
233 ) -> std::result::Result<actix_web::dev::ServerHandle, String> {
234 crate::server::handlers::web::create_web_server(ctx, server_info, config)
236 }
237
238 fn update_server_status(&self, ctx: &ServerContext, server_id: &str, status: ServerStatus) {
239 if let Ok(mut servers) = ctx.servers.write() {
240 if let Some(server) = servers.get_mut(server_id) {
241 server.status = status;
242 }
243 }
244 }
245
246 fn parse_browser_override(args: &[&str]) -> Option<bool> {
248 for arg in args {
249 match *arg {
250 "--no-browser" => return Some(false),
251 "--browser" => return Some(true),
252 _ => continue,
253 }
254 }
255 None
256 }
257}