Skip to main content

rush_sync_server/commands/start/
command.rs

1use crate::commands::command::Command;
2use crate::commands::parsing::{parse_bulk_args, BulkMode};
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(get_translation(
32                "server.error.id_missing",
33                &[],
34            )));
35        }
36
37        let config = get_config()?;
38        let ctx = crate::server::shared::get_shared_context();
39
40        match parse_bulk_args(args) {
41            BulkMode::Single(identifier) => self.start_single_server(&config, ctx, &identifier),
42            BulkMode::Range(start, end) => self.start_range_servers(&config, ctx, start, end),
43            BulkMode::All => self.start_all_servers(&config, ctx),
44            BulkMode::Invalid(error) => Err(AppError::Validation(error)),
45        }
46    }
47
48    fn priority(&self) -> u8 {
49        66
50    }
51}
52
53impl StartCommand {
54    // Start single server (existing robust logic)
55    fn start_single_server(
56        &self,
57        config: &Config,
58        ctx: &ServerContext,
59        identifier: &str,
60    ) -> Result<String> {
61        let (server_info, existing_handle) =
62            {
63                let servers_guard = ctx.servers.read().map_err(|_| {
64                    AppError::Validation("Server-Context lock poisoned".to_string())
65                })?;
66
67                let server_info = find_server(&servers_guard, identifier)?.clone();
68
69                if server_info.status == ServerStatus::Running {
70                    let handles_guard = ctx.handles.read().map_err(|_| {
71                        AppError::Validation("Handle-Context lock poisoned".to_string())
72                    })?;
73
74                    if handles_guard.contains_key(&server_info.id) {
75                        return Ok(format!(
76                            "Server '{}' is already running on http://{}:{}",
77                            server_info.name, config.server.bind_address, server_info.port
78                        ));
79                    }
80                }
81
82                let handles_guard = ctx.handles.read().map_err(|_| {
83                    AppError::Validation("Handle-Context lock poisoned".to_string())
84                })?;
85                let existing_handle = handles_guard.get(&server_info.id).cloned();
86
87                (server_info, existing_handle)
88            };
89
90        if let Some(_handle) = existing_handle {
91            if server_info.status != ServerStatus::Running {
92                self.update_server_status(ctx, &server_info.id, ServerStatus::Running);
93
94                let server_id = server_info.id.clone();
95                tokio::spawn(async move {
96                    crate::server::shared::persist_server_update(&server_id, ServerStatus::Running)
97                        .await;
98                });
99            }
100
101            return Ok(format!(
102                "Server '{}' is already running on http://{}:{} (status corrected)",
103                server_info.name, config.server.bind_address, server_info.port
104            ));
105        }
106
107        // Port validation
108        match self.validate_port_safely(&server_info, &config.server.bind_address) {
109            PortValidationResult::Available => {}
110            PortValidationResult::OccupiedByUs => {
111                return Ok(get_translation(
112                    "server.error.port_used_by_us",
113                    &[&server_info.port.to_string()],
114                ));
115            }
116            PortValidationResult::OccupiedByOther => {
117                return Ok(get_translation(
118                    "server.error.port_used_by_other",
119                    &[&server_info.port.to_string(), &server_info.name],
120                ));
121            }
122        }
123
124        // Server limit check
125        let running_count = self.count_running_servers(ctx);
126        if running_count >= config.server.max_concurrent {
127            return Err(AppError::Validation(format!(
128                "Cannot start server: Running servers limit reached ({}/{})",
129                running_count, config.server.max_concurrent
130            )));
131        }
132
133        self.actually_start_server(config, ctx, server_info, running_count)
134    }
135
136    // Start servers by range (e.g., "start 1-3")
137    fn start_range_servers(
138        &self,
139        config: &Config,
140        ctx: &ServerContext,
141        start: u32,
142        end: u32,
143    ) -> Result<String> {
144        let mut results = Vec::new();
145        let mut started_count = 0;
146        let mut failed_count = 0;
147
148        for i in start..=end {
149            let identifier = format!("{}", i);
150
151            match self.start_single_server(config, ctx, &identifier) {
152                Ok(message) => {
153                    if message.contains("successfully started") {
154                        started_count += 1;
155                        results.push(format!("Server {}: Started", i));
156                    } else {
157                        results.push(format!("Server {}: {}", i, message));
158                    }
159                }
160                Err(e) => {
161                    failed_count += 1;
162                    results.push(format!("Server {}: Failed - {}", i, e));
163
164                    // Stop on critical errors, continue on limit/port issues
165                    if e.to_string().contains("limit reached") {
166                        break;
167                    }
168                }
169            }
170        }
171
172        let summary = format!(
173            "Range start completed: {} started, {} failed (Range: {}-{})",
174            started_count, failed_count, start, end
175        );
176
177        if results.is_empty() {
178            Ok(summary)
179        } else {
180            Ok(format!("{}\n\nResults:\n{}", summary, results.join("\n")))
181        }
182    }
183
184    // Start all stopped servers
185    fn start_all_servers(&self, config: &Config, ctx: &ServerContext) -> Result<String> {
186        let stopped_servers: Vec<_> = {
187            let servers = read_lock(&ctx.servers, "servers")?;
188            servers
189                .values()
190                .filter(|s| s.status == ServerStatus::Stopped)
191                .map(|s| (s.id.clone(), s.name.clone()))
192                .collect()
193        };
194
195        if stopped_servers.is_empty() {
196            return Ok("No stopped servers to start".to_string());
197        }
198
199        if stopped_servers.len() > 50 {
200            log::warn!(
201                "Starting {} servers - this may take a while",
202                stopped_servers.len()
203            );
204        }
205
206        let mut results = Vec::new();
207        let mut started_count = 0;
208        let mut failed_count = 0;
209
210        for (server_id, server_name) in stopped_servers {
211            match self.start_single_server(config, ctx, &server_id) {
212                Ok(message) => {
213                    if message.contains("successfully started") {
214                        started_count += 1;
215                        results.push(format!("{}: Started", server_name));
216                    } else {
217                        results.push(format!("{}: {}", server_name, message));
218                    }
219                }
220                Err(e) => {
221                    failed_count += 1;
222                    results.push(format!("{}: Failed - {}", server_name, e));
223
224                    if e.to_string().contains("limit reached") {
225                        break;
226                    }
227                }
228            }
229        }
230
231        let summary = format!(
232            "Start all completed: {} started, {} failed",
233            started_count, failed_count
234        );
235
236        Ok(format!("{}\n\nResults:\n{}", summary, results.join("\n")))
237    }
238
239    // Actually start the server (extracted from single server logic)
240    fn actually_start_server(
241        &self,
242        config: &Config,
243        ctx: &ServerContext,
244        server_info: crate::server::types::ServerInfo,
245        current_running_count: usize,
246    ) -> Result<String> {
247        match self.spawn_server(config, ctx, server_info.clone()) {
248            Ok(handle) => {
249                {
250                    let mut handles = write_lock(&ctx.handles, "handles")?;
251                    handles.insert(server_info.id.clone(), handle);
252                }
253
254                self.update_server_status(ctx, &server_info.id, ServerStatus::Running);
255
256                let server_id = server_info.id.clone();
257                tokio::spawn(async move {
258                    crate::server::shared::persist_server_update(&server_id, ServerStatus::Running)
259                        .await;
260                });
261
262                let server_url =
263                    format!("http://{}:{}", config.server.bind_address, server_info.port);
264
265                if config.server.auto_open_browser {
266                    self.spawn_browser_opener(server_url.clone(), server_info.name.clone(), config);
267                }
268
269                Ok(format!(
270                    "Server '{}' successfully started on {} [PERSISTENT] ({}/{} running){}",
271                    server_info.name,
272                    server_url,
273                    current_running_count + 1,
274                    config.server.max_concurrent,
275                    if config.server.auto_open_browser {
276                        " - Browser opening..."
277                    } else {
278                        ""
279                    }
280                ))
281            }
282            Err(e) => {
283                self.update_server_status(ctx, &server_info.id, ServerStatus::Failed);
284
285                let server_id = server_info.id.clone();
286                tokio::spawn(async move {
287                    crate::server::shared::persist_server_update(&server_id, ServerStatus::Failed)
288                        .await;
289                });
290
291                Err(AppError::Validation(format!("Server start failed: {}", e)))
292            }
293        }
294    }
295
296    // Helper methods (unchanged from robust version)
297    fn validate_port_safely(
298        &self,
299        server_info: &crate::server::types::ServerInfo,
300        bind_address: &str,
301    ) -> PortValidationResult {
302        if is_port_available(server_info.port, bind_address) {
303            PortValidationResult::Available
304        } else {
305            match crate::server::utils::port::check_port_status(server_info.port, bind_address) {
306                crate::server::utils::port::PortStatus::Available => {
307                    PortValidationResult::Available
308                }
309                crate::server::utils::port::PortStatus::OccupiedByUs => {
310                    PortValidationResult::OccupiedByUs
311                }
312                crate::server::utils::port::PortStatus::OccupiedByOther => {
313                    PortValidationResult::OccupiedByOther
314                }
315            }
316        }
317    }
318
319    fn count_running_servers(&self, ctx: &ServerContext) -> usize {
320        let servers = match ctx.servers.read() {
321            Ok(s) => s,
322            Err(e) => {
323                log::error!("servers lock poisoned: {}", e);
324                return 0;
325            }
326        };
327        servers
328            .values()
329            .filter(|s| s.status == ServerStatus::Running)
330            .count()
331    }
332
333    fn spawn_server(
334        &self,
335        config: &Config,
336        ctx: &ServerContext,
337        server_info: crate::server::types::ServerInfo,
338    ) -> std::result::Result<actix_web::dev::ServerHandle, String> {
339        crate::server::handlers::web::create_web_server(ctx, server_info, config)
340    }
341
342    fn spawn_browser_opener(&self, url: String, name: String, config: &Config) {
343        let delay = config.server.startup_delay_ms.min(2000);
344        tokio::spawn(async move {
345            tokio::time::sleep(tokio::time::Duration::from_millis(delay)).await;
346            if let Err(e) = opener::open(&url) {
347                log::warn!("Failed to open browser for '{}': {}", name, e);
348            }
349        });
350    }
351
352    fn update_server_status(&self, ctx: &ServerContext, server_id: &str, status: ServerStatus) {
353        if let Ok(mut servers) = ctx.servers.write() {
354            if let Some(server) = servers.get_mut(server_id) {
355                server.status = status;
356            }
357        }
358    }
359}
360
361#[derive(Debug)]
362enum PortValidationResult {
363    Available,
364    OccupiedByUs,
365    OccupiedByOther,
366}