rush_sync_server/commands/start/
command.rs

1// Enhanced src/commands/start/command.rs - RANGE & BULK SUPPORT
2use 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    // Parse different start argument patterns
62    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        // "start all"
70        if arg.eq_ignore_ascii_case("all") {
71            return StartMode::All;
72        }
73
74        // "start 1-3" or "start 001-005"
75        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            // Single server by ID/name/number
95            StartMode::Single(arg.to_string())
96        }
97    }
98
99    // Start single server (existing robust logic)
100    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        // Port validation
153        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        // Server limit check
170        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    // Start servers by range (e.g., "start 1-3")
182    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                    // Stop on critical errors, continue on limit/port issues
210                    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    // Start all stopped servers
230    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    // Actually start the server (extracted from single server logic)
284    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    // Helper methods (unchanged from robust version)
340    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}