rush_sync_server/commands/create/
command.rs

1// Fixed 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 a new web server (persistent)"
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
31        let ctx = crate::server::shared::get_shared_context();
32
33        match args.len() {
34            0 => self.create_server(&config, ctx, None, None),
35            1 => {
36                if let Ok(port) = args[0].parse::<u16>() {
37                    self.create_server(&config, ctx, None, Some(port))
38                } else {
39                    self.create_server(&config, ctx, Some(args[0].to_string()), None)
40                }
41            }
42            2 => match args[1].parse::<u16>() {
43                Ok(port) => self.create_server(&config, ctx, Some(args[0].to_string()), Some(port)),
44                Err(_) => Err(AppError::Validation("Invalid port".to_string())),
45            },
46            _ => Err(AppError::Validation("Too many parameters".to_string())),
47        }
48    }
49
50    fn priority(&self) -> u8 {
51        65
52    }
53}
54
55impl CreateCommand {
56    fn create_server(
57        &self,
58        config: &Config,
59        ctx: &ServerContext,
60        custom_name: Option<String>,
61        custom_port: Option<u16>,
62    ) -> Result<String> {
63        let id = Uuid::new_v4().to_string();
64        let has_custom_name = custom_name.is_some();
65        let has_custom_port = custom_port.is_some();
66
67        let name = if let Some(custom_name) = custom_name {
68            validate_server_name(&custom_name)?;
69            let servers = ctx.servers.read().unwrap();
70            if servers.values().any(|s| s.name == custom_name) {
71                return Err(AppError::Validation(format!(
72                    "Server-Name '{}' bereits vergeben!",
73                    custom_name
74                )));
75            }
76            custom_name
77        } else {
78            let server_number = self.find_next_server_number(ctx);
79            format!("rss-{:03}", server_number)
80        };
81
82        let port = if let Some(custom_port) = custom_port {
83            // Use configurable minimum port from config
84            let min_port = config.server.port_range_start.max(1024);
85            if custom_port < min_port {
86                return Err(AppError::Validation(format!(
87                    "Port must be >= {} (configured minimum: {})",
88                    min_port, config.server.port_range_start
89                )));
90            }
91
92            // Check if port is within configured range
93            if custom_port > config.server.port_range_end {
94                return Err(AppError::Validation(format!(
95                    "Port {} exceeds configured maximum: {}",
96                    custom_port, config.server.port_range_end
97                )));
98            }
99
100            let servers = ctx.servers.read().unwrap();
101            if servers.values().any(|s| s.port == custom_port) {
102                return Err(AppError::Validation(format!(
103                    "Port {} bereits verwendet!",
104                    custom_port
105                )));
106            }
107            if !crate::server::utils::port::is_port_available(custom_port) {
108                return Err(AppError::Validation(format!(
109                    "Port {} bereits belegt!",
110                    custom_port
111                )));
112            }
113
114            custom_port
115        } else {
116            self.find_next_available_port(config)?
117        };
118
119        // Check server count limit
120        let current_server_count = ctx.servers.read().unwrap().len();
121        if current_server_count >= config.server.max_concurrent {
122            return Err(AppError::Validation(format!(
123                "Maximum server limit reached: {}/{}. Use 'cleanup' to remove stopped servers or increase max_concurrent in config.",
124                current_server_count, config.server.max_concurrent
125            )));
126        }
127
128        let timestamp = std::time::SystemTime::now()
129            .duration_since(std::time::UNIX_EPOCH)
130            .unwrap_or_default()
131            .as_secs();
132
133        let server_info = ServerInfo {
134            id: id.clone(),
135            name: name.clone(),
136            port,
137            status: ServerStatus::Stopped,
138            created_at: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
139            created_timestamp: timestamp,
140        };
141
142        // Verzeichnis sofort bei CREATE erstellen
143        if let Err(e) = crate::server::handlers::web::create_server_directory_and_files(&name, port)
144        {
145            return Err(AppError::Validation(format!(
146                "Failed to create server directory: {}",
147                e
148            )));
149        }
150
151        // Add to runtime context
152        ctx.servers
153            .write()
154            .unwrap()
155            .insert(id.clone(), server_info.clone());
156
157        // Persist to file (async)
158        let registry = crate::server::shared::get_persistent_registry();
159        let server_info_clone = server_info.clone();
160        tokio::spawn(async move {
161            if let Ok(_persistent_servers) = registry.load_servers().await {
162                if let Err(e) = registry.add_server(server_info_clone).await {
163                    log::error!("Failed to persist server: {}", e);
164                }
165            }
166        });
167
168        let success_msg = if has_custom_name || has_custom_port {
169            format!(
170                "Custom Server created: '{}' (ID: {}) on port {} [PERSISTENT] ({}/{} servers)",
171                name,
172                &id[0..8],
173                port,
174                current_server_count + 1,
175                config.server.max_concurrent
176            )
177        } else {
178            format!(
179                "Server created: '{}' (ID: {}) on port {} [PERSISTENT] ({}/{} servers)",
180                name,
181                &id[0..8],
182                port,
183                current_server_count + 1,
184                config.server.max_concurrent
185            )
186        };
187
188        Ok(success_msg)
189    }
190
191    // Updated to use config instead of context
192    fn find_next_available_port(&self, config: &Config) -> Result<u16> {
193        let ctx = crate::server::shared::get_shared_context();
194        let used_ports: std::collections::HashSet<u16> = {
195            let servers = ctx
196                .servers
197                .read()
198                .map_err(|_| AppError::Validation("Server-Context lock poisoned".to_string()))?;
199            servers.values().map(|s| s.port).collect()
200        };
201
202        let start_port = config.server.port_range_start;
203        let end_port = config.server.port_range_end;
204
205        // Sicherheitscheck für Port-Range
206        if start_port >= end_port {
207            return Err(AppError::Validation(format!(
208                "Invalid port range: {} >= {}. Check config.",
209                start_port, end_port
210            )));
211        }
212
213        // Effizienter: Nicht mehr als 1000 Ports testen
214        let max_attempts = ((end_port - start_port + 1) as usize).min(1000);
215
216        for i in 0..max_attempts {
217            let candidate_port = start_port + (i as u16);
218
219            if candidate_port > end_port {
220                break;
221            }
222
223            if !used_ports.contains(&candidate_port)
224                && crate::server::utils::port::is_port_available(candidate_port)
225            {
226                return Ok(candidate_port);
227            }
228        }
229
230        Err(AppError::Validation(format!(
231            "No available ports in range {}-{} after {} attempts",
232            start_port, end_port, max_attempts
233        )))
234    }
235
236    fn find_next_server_number(&self, ctx: &ServerContext) -> u32 {
237        let servers = ctx.servers.read().unwrap();
238        let mut existing_numbers = Vec::new();
239
240        for server in servers.values() {
241            if let Some(number_str) = server.name.strip_prefix("rss-") {
242                if let Ok(number) = number_str.parse::<u32>() {
243                    existing_numbers.push(number);
244                }
245            }
246        }
247
248        existing_numbers.sort();
249        let mut next_number = 1;
250        for &existing in &existing_numbers {
251            if existing == next_number {
252                next_number += 1;
253            } else {
254                break;
255            }
256        }
257        next_number
258    }
259}