Skip to main content

rush_sync_server/commands/create/
command.rs

1// src/commands/create/command.rs
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                // Check for port (4-5 digits) first, then count (1-2 digits)
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 <= 100 {
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 > 100 {
123                        return CreationMode::Invalid(
124                            "Maximum 100 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 = read_lock(&ctx.servers, "servers")?.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 = read_lock(&ctx.servers, "servers")?.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
244        let name = if let Some(custom_name) = custom_name {
245            validate_server_name(&custom_name)?;
246            let servers = read_lock(&ctx.servers, "servers")?;
247            if servers.values().any(|s| s.name == custom_name) {
248                return Err(AppError::Validation(get_translation(
249                    "server.error.name_taken",
250                    &[&custom_name],
251                )));
252            }
253            custom_name
254        } else {
255            let server_number = self.find_next_server_number(ctx);
256            format!("rss-{:03}", server_number)
257        };
258
259        let port = if let Some(custom_port) = custom_port {
260            let min_port = config.server.port_range_start.max(1024);
261            if custom_port < min_port {
262                return Err(AppError::Validation(format!(
263                    "Port must be >= {} (configured minimum: {})",
264                    min_port, config.server.port_range_start
265                )));
266            }
267
268            if custom_port > config.server.port_range_end {
269                return Err(AppError::Validation(format!(
270                    "Port {} exceeds configured maximum: {}",
271                    custom_port, config.server.port_range_end
272                )));
273            }
274
275            let servers = read_lock(&ctx.servers, "servers")?;
276            if servers.values().any(|s| s.port == custom_port) {
277                return Err(AppError::Validation(get_translation(
278                    "server.error.port_used",
279                    &[&custom_port.to_string()],
280                )));
281            }
282            if !crate::server::utils::port::is_port_available(
283                custom_port,
284                &config.server.bind_address,
285            ) {
286                return Err(AppError::Validation(get_translation(
287                    "server.error.port_occupied",
288                    &[&custom_port.to_string()],
289                )));
290            }
291
292            custom_port
293        } else {
294            self.find_next_available_port(config)?
295        };
296
297        let timestamp = std::time::SystemTime::now()
298            .duration_since(std::time::UNIX_EPOCH)
299            .unwrap_or_default()
300            .as_secs();
301
302        let server_info = ServerInfo {
303            id: id.clone(),
304            name: name.clone(),
305            port,
306            status: ServerStatus::Stopped,
307            created_at: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
308            created_timestamp: timestamp,
309        };
310
311        // Create server directory and files
312        if let Err(e) = crate::server::handlers::web::create_server_directory_and_files(&name, port)
313        {
314            return Err(AppError::Validation(format!(
315                "Failed to create server directory: {}",
316                e
317            )));
318        }
319
320        // Add to runtime context
321        write_lock(&ctx.servers, "servers")?.insert(id.clone(), server_info.clone());
322
323        // Persist to file (async)
324        let registry = crate::server::shared::get_persistent_registry();
325        let server_info_clone = server_info.clone();
326        tokio::spawn(async move {
327            if let Ok(_persistent_servers) = registry.load_servers().await {
328                if let Err(e) = registry.add_server(server_info_clone).await {
329                    log::error!("Failed to persist server: {}", e);
330                }
331            }
332        });
333
334        let summary = format!(
335            "'{}' (ID: {}) on port {} [PERSISTENT]",
336            name,
337            &id[0..8],
338            port
339        );
340
341        Ok(ServerCreationResult { name, summary })
342    }
343
344    // Existing helper methods (unchanged)
345    fn find_next_available_port(&self, config: &Config) -> Result<u16> {
346        let ctx = crate::server::shared::get_shared_context();
347        let used_ports: std::collections::HashSet<u16> = {
348            let servers = ctx
349                .servers
350                .read()
351                .map_err(|_| AppError::Validation("Server-Context lock poisoned".to_string()))?;
352            servers.values().map(|s| s.port).collect()
353        };
354
355        let start_port = config.server.port_range_start;
356        let end_port = config.server.port_range_end;
357
358        if start_port >= end_port {
359            return Err(AppError::Validation(format!(
360                "Invalid port range: {} >= {}. Check config.",
361                start_port, end_port
362            )));
363        }
364
365        let max_attempts = ((end_port - start_port + 1) as usize).min(1000);
366
367        for i in 0..max_attempts {
368            let candidate_port = start_port + (i as u16);
369
370            if candidate_port > end_port {
371                break;
372            }
373
374            if !used_ports.contains(&candidate_port)
375                && crate::server::utils::port::is_port_available(
376                    candidate_port,
377                    &config.server.bind_address,
378                )
379            {
380                return Ok(candidate_port);
381            }
382        }
383
384        Err(AppError::Validation(format!(
385            "No available ports in range {}-{} after {} attempts",
386            start_port, end_port, max_attempts
387        )))
388    }
389
390    fn find_next_server_number(&self, ctx: &ServerContext) -> u32 {
391        let servers = match ctx.servers.read() {
392            Ok(s) => s,
393            Err(e) => {
394                log::error!("servers lock poisoned: {}", e);
395                return 1;
396            }
397        };
398        let mut existing_numbers = Vec::new();
399
400        for server in servers.values() {
401            if let Some(number_str) = server.name.strip_prefix("rss-") {
402                if let Ok(number) = number_str.parse::<u32>() {
403                    existing_numbers.push(number);
404                }
405            }
406        }
407
408        existing_numbers.sort();
409        let mut next_number = 1;
410        for &existing in &existing_numbers {
411            if existing == next_number {
412                next_number += 1;
413            } else {
414                break;
415            }
416        }
417        next_number
418    }
419}
420
421#[derive(Debug)]
422struct ServerCreationResult {
423    name: String,
424    summary: String,
425}