rush_sync_server/commands/create/
command.rs

1// Enhanced src/commands/create/command.rs - BULK CREATION SUPPORT
2use crate::commands::command::Command;
3use crate::core::prelude::*;
4use crate::server::types::{ServerContext, ServerInfo, ServerStatus};
5use crate::server::utils::validation::validate_server_name;
6use uuid::Uuid;
7
8#[derive(Debug, Default)]
9pub struct CreateCommand;
10
11impl CreateCommand {
12    pub fn new() -> Self {
13        Self
14    }
15}
16
17impl Command for CreateCommand {
18    fn name(&self) -> &'static str {
19        "create"
20    }
21    fn description(&self) -> &'static str {
22        "Create web server(s) - supports bulk creation"
23    }
24    fn matches(&self, command: &str) -> bool {
25        command.trim().to_lowercase().starts_with("create")
26    }
27
28    fn execute_sync(&self, args: &[&str]) -> Result<String> {
29        let config = get_config()?;
30        let ctx = crate::server::shared::get_shared_context();
31
32        // Parse arguments for different creation modes
33        match self.parse_creation_args(args) {
34            CreationMode::Single { name, port } => {
35                self.create_single_server(&config, ctx, name, port)
36            }
37            CreationMode::BulkAuto { count } => {
38                self.create_bulk_servers(&config, ctx, count, None, None)
39            }
40            CreationMode::BulkWithBase {
41                base_name,
42                base_port,
43                count,
44            } => self.create_bulk_servers(&config, ctx, count, Some(base_name), Some(base_port)),
45            CreationMode::Invalid(error) => Err(AppError::Validation(error)),
46        }
47    }
48
49    fn priority(&self) -> u8 {
50        65
51    }
52}
53
54#[derive(Debug)]
55enum CreationMode {
56    Single {
57        name: Option<String>,
58        port: Option<u16>,
59    },
60    BulkAuto {
61        count: u32,
62    },
63    BulkWithBase {
64        base_name: String,
65        base_port: u16,
66        count: u32,
67    },
68    Invalid(String),
69}
70
71impl CreateCommand {
72    // Argument parsing logic
73    fn parse_creation_args(&self, args: &[&str]) -> CreationMode {
74        match args.len() {
75            0 => CreationMode::Single {
76                name: None,
77                port: None,
78            },
79
80            1 => {
81                // Erst auf Port prüfen (4-5 Stellen), dann auf Count (1-2 Stellen)
82                if let Ok(port) = args[0].parse::<u16>() {
83                    if port >= 1000 {
84                        // "create 8080" -> Single server with port
85                        CreationMode::Single {
86                            name: None,
87                            port: Some(port),
88                        }
89                    } else if port > 0 && port <= 50 {
90                        // "create 5" -> Bulk creation with count
91                        CreationMode::BulkAuto { count: port as u32 }
92                    } else {
93                        CreationMode::Invalid("Invalid port or count".to_string())
94                    }
95                } else {
96                    // "create myserver" -> Single server with name
97                    CreationMode::Single {
98                        name: Some(args[0].to_string()),
99                        port: None,
100                    }
101                }
102            }
103
104            2 => {
105                // "create myserver 8080" -> Single with name and port
106                if let Ok(port) = args[1].parse::<u16>() {
107                    CreationMode::Single {
108                        name: Some(args[0].to_string()),
109                        port: Some(port),
110                    }
111                } else {
112                    CreationMode::Invalid("Invalid port number".to_string())
113                }
114            }
115
116            3 => {
117                // "create myserver 8080 5" -> Bulk with base name, port, and count
118                if let (Ok(port), Ok(count)) = (args[1].parse::<u16>(), args[2].parse::<u32>()) {
119                    if count == 0 {
120                        return CreationMode::Invalid("Count must be > 0".to_string());
121                    }
122                    if count > 50 {
123                        return CreationMode::Invalid(
124                            "Maximum 50 servers per bulk operation".to_string(),
125                        );
126                    }
127                    CreationMode::BulkWithBase {
128                        base_name: args[0].to_string(),
129                        base_port: port,
130                        count,
131                    }
132                } else {
133                    CreationMode::Invalid("Invalid port or count".to_string())
134                }
135            }
136
137            _ => CreationMode::Invalid(
138                "Too many arguments. Usage: create [name] [port] [count]".to_string(),
139            ),
140        }
141    }
142
143    // Single server creation (existing logic)
144    fn create_single_server(
145        &self,
146        config: &Config,
147        ctx: &ServerContext,
148        custom_name: Option<String>,
149        custom_port: Option<u16>,
150    ) -> Result<String> {
151        let result = self.create_server_internal(config, ctx, custom_name, custom_port)?;
152        Ok(format!("Server created: {}", result.summary))
153    }
154
155    // Bulk server creation
156    fn create_bulk_servers(
157        &self,
158        config: &Config,
159        ctx: &ServerContext,
160        count: u32,
161        base_name: Option<String>,
162        base_port: Option<u16>,
163    ) -> Result<String> {
164        let initial_server_count = ctx.servers.read().unwrap().len();
165
166        // Check if bulk creation would exceed limits
167        if initial_server_count + (count as usize) > config.server.max_concurrent {
168            return Err(AppError::Validation(format!(
169                "Bulk creation would exceed server limit: {} + {} > {} (max_concurrent)",
170                initial_server_count, count, config.server.max_concurrent
171            )));
172        }
173
174        let mut created_servers = Vec::new();
175        let mut failed_servers = Vec::new();
176
177        for i in 0..count {
178            let (name, port) =
179                if let (Some(ref base_name), Some(base_port)) = (&base_name, base_port) {
180                    // Use base name with suffix and increment port
181                    let name = format!("{}-{:03}", base_name, i + 1);
182                    let port = base_port + (i as u16);
183                    (Some(name), Some(port))
184                } else {
185                    // Use automatic naming and port assignment (both None for auto)
186                    (None, None)
187                };
188
189            match self.create_server_internal(config, ctx, name, port) {
190                Ok(result) => {
191                    created_servers.push(result);
192                }
193                Err(e) => {
194                    failed_servers.push(format!("Server {}: {}", i + 1, e));
195
196                    // Stop on critical errors, continue on port conflicts
197                    if !e.to_string().contains("bereits") && !e.to_string().contains("occupied") {
198                        break;
199                    }
200                }
201            }
202        }
203
204        // Format results
205        let mut result = format!(
206            "Bulk creation completed: {} of {} servers created",
207            created_servers.len(),
208            count
209        );
210
211        if !created_servers.is_empty() {
212            result.push_str("\n\nCreated servers:");
213            for server in &created_servers {
214                result.push_str(&format!("\n  {} - {}", server.name, server.summary));
215            }
216        }
217
218        if !failed_servers.is_empty() {
219            result.push_str("\n\nFailed:");
220            for failure in &failed_servers {
221                result.push_str(&format!("\n  {}", failure));
222            }
223        }
224
225        let final_count = ctx.servers.read().unwrap().len();
226        result.push_str(&format!(
227            "\n\nTotal servers: {}/{}",
228            final_count, config.server.max_concurrent
229        ));
230
231        Ok(result)
232    }
233
234    // Internal server creation logic (extracted from original)
235    fn create_server_internal(
236        &self,
237        config: &Config,
238        ctx: &ServerContext,
239        custom_name: Option<String>,
240        custom_port: Option<u16>,
241    ) -> Result<ServerCreationResult> {
242        let id = Uuid::new_v4().to_string();
243        let has_custom_name = custom_name.is_some();
244
245        let name = if let Some(custom_name) = custom_name {
246            validate_server_name(&custom_name)?;
247            let servers = ctx.servers.read().unwrap();
248            if servers.values().any(|s| s.name == custom_name) {
249                return Err(AppError::Validation(format!(
250                    "Server-Name '{}' bereits vergeben!",
251                    custom_name
252                )));
253            }
254            custom_name
255        } else {
256            let server_number = self.find_next_server_number(ctx);
257            format!("rss-{:03}", server_number)
258        };
259
260        let port = if let Some(custom_port) = custom_port {
261            let min_port = config.server.port_range_start.max(1024);
262            if custom_port < min_port {
263                return Err(AppError::Validation(format!(
264                    "Port must be >= {} (configured minimum: {})",
265                    min_port, config.server.port_range_start
266                )));
267            }
268
269            if custom_port > config.server.port_range_end {
270                return Err(AppError::Validation(format!(
271                    "Port {} exceeds configured maximum: {}",
272                    custom_port, config.server.port_range_end
273                )));
274            }
275
276            let servers = ctx.servers.read().unwrap();
277            if servers.values().any(|s| s.port == custom_port) {
278                return Err(AppError::Validation(format!(
279                    "Port {} bereits verwendet!",
280                    custom_port
281                )));
282            }
283            if !crate::server::utils::port::is_port_available(custom_port) {
284                return Err(AppError::Validation(format!(
285                    "Port {} bereits belegt!",
286                    custom_port
287                )));
288            }
289
290            custom_port
291        } else {
292            self.find_next_available_port(config)?
293        };
294
295        let timestamp = std::time::SystemTime::now()
296            .duration_since(std::time::UNIX_EPOCH)
297            .unwrap_or_default()
298            .as_secs();
299
300        let server_info = ServerInfo {
301            id: id.clone(),
302            name: name.clone(),
303            port,
304            status: ServerStatus::Stopped,
305            created_at: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
306            created_timestamp: timestamp,
307        };
308
309        // Create server directory and files
310        if let Err(e) = crate::server::handlers::web::create_server_directory_and_files(&name, port)
311        {
312            return Err(AppError::Validation(format!(
313                "Failed to create server directory: {}",
314                e
315            )));
316        }
317
318        // Add to runtime context
319        ctx.servers
320            .write()
321            .unwrap()
322            .insert(id.clone(), server_info.clone());
323
324        // Persist to file (async)
325        let registry = crate::server::shared::get_persistent_registry();
326        let server_info_clone = server_info.clone();
327        tokio::spawn(async move {
328            if let Ok(_persistent_servers) = registry.load_servers().await {
329                if let Err(e) = registry.add_server(server_info_clone).await {
330                    log::error!("Failed to persist server: {}", e);
331                }
332            }
333        });
334
335        let summary = if has_custom_name {
336            format!(
337                "'{}' (ID: {}) on port {} [PERSISTENT]",
338                name,
339                &id[0..8],
340                port
341            )
342        } else {
343            format!(
344                "'{}' (ID: {}) on port {} [PERSISTENT]",
345                name,
346                &id[0..8],
347                port
348            )
349        };
350
351        Ok(ServerCreationResult { name, summary })
352    }
353
354    // Existing helper methods (unchanged)
355    fn find_next_available_port(&self, config: &Config) -> Result<u16> {
356        let ctx = crate::server::shared::get_shared_context();
357        let used_ports: std::collections::HashSet<u16> = {
358            let servers = ctx
359                .servers
360                .read()
361                .map_err(|_| AppError::Validation("Server-Context lock poisoned".to_string()))?;
362            servers.values().map(|s| s.port).collect()
363        };
364
365        let start_port = config.server.port_range_start;
366        let end_port = config.server.port_range_end;
367
368        if start_port >= end_port {
369            return Err(AppError::Validation(format!(
370                "Invalid port range: {} >= {}. Check config.",
371                start_port, end_port
372            )));
373        }
374
375        let max_attempts = ((end_port - start_port + 1) as usize).min(1000);
376
377        for i in 0..max_attempts {
378            let candidate_port = start_port + (i as u16);
379
380            if candidate_port > end_port {
381                break;
382            }
383
384            if !used_ports.contains(&candidate_port)
385                && crate::server::utils::port::is_port_available(candidate_port)
386            {
387                return Ok(candidate_port);
388            }
389        }
390
391        Err(AppError::Validation(format!(
392            "No available ports in range {}-{} after {} attempts",
393            start_port, end_port, max_attempts
394        )))
395    }
396
397    fn find_next_server_number(&self, ctx: &ServerContext) -> u32 {
398        let servers = ctx.servers.read().unwrap();
399        let mut existing_numbers = Vec::new();
400
401        for server in servers.values() {
402            if let Some(number_str) = server.name.strip_prefix("rss-") {
403                if let Ok(number) = number_str.parse::<u32>() {
404                    existing_numbers.push(number);
405                }
406            }
407        }
408
409        existing_numbers.sort();
410        let mut next_number = 1;
411        for &existing in &existing_numbers {
412            if existing == next_number {
413                next_number += 1;
414            } else {
415                break;
416            }
417        }
418        next_number
419    }
420}
421
422#[derive(Debug)]
423struct ServerCreationResult {
424    name: String,
425    summary: String,
426}