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 server(s) - supports ranges and bulk operations"
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>', 'start 1-3', 'start all'".to_string(),
33 ));
34 }
35
36 let config = get_config()?;
37 let ctx = crate::server::shared::get_shared_context();
38
39 match self.parse_start_args(args) {
40 StartMode::Single(identifier) => self.start_single_server(&config, ctx, &identifier),
41 StartMode::Range(start, end) => self.start_range_servers(&config, ctx, start, end),
42 StartMode::All => self.start_all_servers(&config, ctx),
43 StartMode::Invalid(error) => Err(AppError::Validation(error)),
44 }
45 }
46
47 fn priority(&self) -> u8 {
48 66
49 }
50}
51
52#[derive(Debug)]
53enum StartMode {
54 Single(String),
55 Range(u32, u32),
56 All,
57 Invalid(String),
58}
59
60impl StartCommand {
61 fn parse_start_args(&self, args: &[&str]) -> StartMode {
63 if args.len() != 1 {
64 return StartMode::Invalid("Too many arguments".to_string());
65 }
66
67 let arg = args[0];
68
69 if arg.eq_ignore_ascii_case("all") {
71 return StartMode::All;
72 }
73
74 if let Some((start_str, end_str)) = arg.split_once('-') {
76 match (start_str.parse::<u32>(), end_str.parse::<u32>()) {
77 (Ok(start), Ok(end)) => {
78 if start == 0 || end == 0 {
79 return StartMode::Invalid("Range indices must be > 0".to_string());
80 }
81 if start > end {
82 return StartMode::Invalid("Start must be <= end in range".to_string());
83 }
84 if end - start > 20 {
85 return StartMode::Invalid(
86 "Maximum 20 servers in range operation".to_string(),
87 );
88 }
89 StartMode::Range(start, end)
90 }
91 _ => StartMode::Single(arg.to_string()),
92 }
93 } else {
94 StartMode::Single(arg.to_string())
96 }
97 }
98
99 fn start_single_server(
101 &self,
102 config: &Config,
103 ctx: &ServerContext,
104 identifier: &str,
105 ) -> Result<String> {
106 let (server_info, existing_handle) =
107 {
108 let servers_guard = ctx.servers.read().map_err(|_| {
109 AppError::Validation("Server-Context lock poisoned".to_string())
110 })?;
111
112 let server_info = find_server(&servers_guard, identifier)?.clone();
113
114 if server_info.status == ServerStatus::Running {
115 let handles_guard = ctx.handles.read().map_err(|_| {
116 AppError::Validation("Handle-Context lock poisoned".to_string())
117 })?;
118
119 if handles_guard.contains_key(&server_info.id) {
120 return Ok(format!(
121 "Server '{}' is already running on http://127.0.0.1:{}",
122 server_info.name, server_info.port
123 ));
124 }
125 }
126
127 let handles_guard = ctx.handles.read().map_err(|_| {
128 AppError::Validation("Handle-Context lock poisoned".to_string())
129 })?;
130 let existing_handle = handles_guard.get(&server_info.id).cloned();
131
132 (server_info, existing_handle)
133 };
134
135 if let Some(_handle) = existing_handle {
136 if server_info.status != ServerStatus::Running {
137 self.update_server_status(ctx, &server_info.id, ServerStatus::Running);
138
139 let server_id = server_info.id.clone();
140 tokio::spawn(async move {
141 crate::server::shared::persist_server_update(&server_id, ServerStatus::Running)
142 .await;
143 });
144 }
145
146 return Ok(format!(
147 "Server '{}' is already running on http://127.0.0.1:{} (status corrected)",
148 server_info.name, server_info.port
149 ));
150 }
151
152 match self.validate_port_safely(&server_info) {
154 PortValidationResult::Available => {}
155 PortValidationResult::OccupiedByUs => {
156 return Ok(format!(
157 "Port {} wird bereits von unserem System verwendet",
158 server_info.port
159 ));
160 }
161 PortValidationResult::OccupiedByOther => {
162 return Ok(format!(
163 "Port {} ist von anderem Prozess belegt! Server '{}' bleibt gestoppt.",
164 server_info.port, server_info.name
165 ));
166 }
167 }
168
169 let running_count = self.count_running_servers(ctx);
171 if running_count >= config.server.max_concurrent {
172 return Err(AppError::Validation(format!(
173 "Cannot start server: Running servers limit reached ({}/{})",
174 running_count, config.server.max_concurrent
175 )));
176 }
177
178 self.actually_start_server(config, ctx, server_info, running_count)
179 }
180
181 fn start_range_servers(
183 &self,
184 config: &Config,
185 ctx: &ServerContext,
186 start: u32,
187 end: u32,
188 ) -> Result<String> {
189 let mut results = Vec::new();
190 let mut started_count = 0;
191 let mut failed_count = 0;
192
193 for i in start..=end {
194 let identifier = format!("{}", i);
195
196 match self.start_single_server(config, ctx, &identifier) {
197 Ok(message) => {
198 if message.contains("successfully started") {
199 started_count += 1;
200 results.push(format!("Server {}: Started", i));
201 } else {
202 results.push(format!("Server {}: {}", i, message));
203 }
204 }
205 Err(e) => {
206 failed_count += 1;
207 results.push(format!("Server {}: Failed - {}", i, e));
208
209 if e.to_string().contains("limit reached") {
211 break;
212 }
213 }
214 }
215 }
216
217 let summary = format!(
218 "Range start completed: {} started, {} failed (Range: {}-{})",
219 started_count, failed_count, start, end
220 );
221
222 if results.is_empty() {
223 Ok(summary)
224 } else {
225 Ok(format!("{}\n\nResults:\n{}", summary, results.join("\n")))
226 }
227 }
228
229 fn start_all_servers(&self, config: &Config, ctx: &ServerContext) -> Result<String> {
231 let stopped_servers: Vec<_> = {
232 let servers = ctx.servers.read().unwrap();
233 servers
234 .values()
235 .filter(|s| s.status == ServerStatus::Stopped)
236 .map(|s| (s.id.clone(), s.name.clone()))
237 .collect()
238 };
239
240 if stopped_servers.is_empty() {
241 return Ok("No stopped servers to start".to_string());
242 }
243
244 if stopped_servers.len() > 20 {
245 return Err(AppError::Validation(
246 "Too many servers to start at once (max 20). Use ranges instead.".to_string(),
247 ));
248 }
249
250 let mut results = Vec::new();
251 let mut started_count = 0;
252 let mut failed_count = 0;
253
254 for (server_id, server_name) in stopped_servers {
255 match self.start_single_server(config, ctx, &server_id) {
256 Ok(message) => {
257 if message.contains("successfully started") {
258 started_count += 1;
259 results.push(format!("{}: Started", server_name));
260 } else {
261 results.push(format!("{}: {}", server_name, message));
262 }
263 }
264 Err(e) => {
265 failed_count += 1;
266 results.push(format!("{}: Failed - {}", server_name, e));
267
268 if e.to_string().contains("limit reached") {
269 break;
270 }
271 }
272 }
273 }
274
275 let summary = format!(
276 "Start all completed: {} started, {} failed",
277 started_count, failed_count
278 );
279
280 Ok(format!("{}\n\nResults:\n{}", summary, results.join("\n")))
281 }
282
283 fn actually_start_server(
285 &self,
286 config: &Config,
287 ctx: &ServerContext,
288 server_info: crate::server::types::ServerInfo,
289 current_running_count: usize,
290 ) -> Result<String> {
291 match self.spawn_server(config, ctx, server_info.clone()) {
292 Ok(handle) => {
293 {
294 let mut handles = ctx.handles.write().unwrap();
295 handles.insert(server_info.id.clone(), handle);
296 }
297
298 self.update_server_status(ctx, &server_info.id, ServerStatus::Running);
299
300 let server_id = server_info.id.clone();
301 tokio::spawn(async move {
302 crate::server::shared::persist_server_update(&server_id, ServerStatus::Running)
303 .await;
304 });
305
306 let server_url = format!("http://127.0.0.1:{}", server_info.port);
307
308 if config.server.auto_open_browser {
309 self.spawn_browser_opener(server_url.clone(), server_info.name.clone(), config);
310 }
311
312 Ok(format!(
313 "Server '{}' successfully started on {} [PERSISTENT] ({}/{} running){}",
314 server_info.name,
315 server_url,
316 current_running_count + 1,
317 config.server.max_concurrent,
318 if config.server.auto_open_browser {
319 " - Browser opening..."
320 } else {
321 ""
322 }
323 ))
324 }
325 Err(e) => {
326 self.update_server_status(ctx, &server_info.id, ServerStatus::Failed);
327
328 let server_id = server_info.id.clone();
329 tokio::spawn(async move {
330 crate::server::shared::persist_server_update(&server_id, ServerStatus::Failed)
331 .await;
332 });
333
334 Err(AppError::Validation(format!("Server start failed: {}", e)))
335 }
336 }
337 }
338
339 fn validate_port_safely(
341 &self,
342 server_info: &crate::server::types::ServerInfo,
343 ) -> PortValidationResult {
344 if is_port_available(server_info.port) {
345 PortValidationResult::Available
346 } else {
347 match crate::server::utils::port::check_port_status(server_info.port) {
348 crate::server::utils::port::PortStatus::Available => {
349 PortValidationResult::Available
350 }
351 crate::server::utils::port::PortStatus::OccupiedByUs => {
352 PortValidationResult::OccupiedByUs
353 }
354 crate::server::utils::port::PortStatus::OccupiedByOther => {
355 PortValidationResult::OccupiedByOther
356 }
357 }
358 }
359 }
360
361 fn count_running_servers(&self, ctx: &ServerContext) -> usize {
362 let servers = ctx.servers.read().unwrap();
363 servers
364 .values()
365 .filter(|s| s.status == ServerStatus::Running)
366 .count()
367 }
368
369 fn spawn_server(
370 &self,
371 config: &Config,
372 ctx: &ServerContext,
373 server_info: crate::server::types::ServerInfo,
374 ) -> std::result::Result<actix_web::dev::ServerHandle, String> {
375 crate::server::handlers::web::create_web_server(ctx, server_info, config)
376 }
377
378 fn spawn_browser_opener(&self, url: String, name: String, config: &Config) {
379 let delay = config.server.startup_delay_ms.min(2000);
380 tokio::spawn(async move {
381 tokio::time::sleep(tokio::time::Duration::from_millis(delay as u64)).await;
382 if let Err(e) = opener::open(&url) {
383 log::warn!("Failed to open browser for '{}': {}", name, e);
384 }
385 });
386 }
387
388 fn update_server_status(&self, ctx: &ServerContext, server_id: &str, status: ServerStatus) {
389 if let Ok(mut servers) = ctx.servers.write() {
390 if let Some(server) = servers.get_mut(server_id) {
391 server.status = status;
392 }
393 }
394 }
395}
396
397#[derive(Debug)]
398enum PortValidationResult {
399 Available,
400 OccupiedByUs,
401 OccupiedByOther,
402}