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 std::future::Future;
7use std::pin::Pin;
8use uuid::Uuid;
9
10#[derive(Debug, Default)]
11pub struct CreateCommand;
12
13impl CreateCommand {
14    pub fn new() -> Self {
15        Self
16    }
17}
18
19impl Command for CreateCommand {
20    fn name(&self) -> &'static str {
21        "create"
22    }
23    fn description(&self) -> &'static str {
24        "Create a new web server (persistent)"
25    }
26    fn matches(&self, command: &str) -> bool {
27        command.trim().to_lowercase().starts_with("create")
28    }
29
30    fn execute_sync(&self, args: &[&str]) -> Result<String> {
31        // DON'T use block_on - instead use spawn_blocking for config loading
32        let config_result = std::thread::spawn(|| {
33            let rt = tokio::runtime::Runtime::new().unwrap();
34            rt.block_on(Config::load())
35        })
36        .join()
37        .map_err(|_| AppError::Validation("Failed to load config".to_string()))??;
38
39        let ctx = crate::server::shared::get_shared_context();
40
41        match args.len() {
42            0 => self.create_server(&config_result, ctx, None, None),
43            1 => {
44                if let Ok(port) = args[0].parse::<u16>() {
45                    self.create_server(&config_result, ctx, None, Some(port))
46                } else {
47                    self.create_server(&config_result, ctx, Some(args[0].to_string()), None)
48                }
49            }
50            2 => match args[1].parse::<u16>() {
51                Ok(port) => {
52                    self.create_server(&config_result, ctx, Some(args[0].to_string()), Some(port))
53                }
54                Err(_) => Err(AppError::Validation("Invalid port".to_string())),
55            },
56            _ => Err(AppError::Validation("Too many parameters".to_string())),
57        }
58    }
59
60    fn execute_async<'a>(
61        &'a self,
62        args: &'a [&'a str],
63    ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'a>> {
64        Box::pin(async move {
65            let config = Config::load().await?;
66            let ctx = crate::server::shared::get_shared_context();
67
68            match args.len() {
69                0 => self.create_server(&config, ctx, None, None),
70                1 => {
71                    if let Ok(port) = args[0].parse::<u16>() {
72                        self.create_server(&config, ctx, None, Some(port))
73                    } else {
74                        self.create_server(&config, ctx, Some(args[0].to_string()), None)
75                    }
76                }
77                2 => match args[1].parse::<u16>() {
78                    Ok(port) => {
79                        self.create_server(&config, ctx, Some(args[0].to_string()), Some(port))
80                    }
81                    Err(_) => Err(AppError::Validation("Invalid port".to_string())),
82                },
83                _ => Err(AppError::Validation("Too many parameters".to_string())),
84            }
85        })
86    }
87
88    fn supports_async(&self) -> bool {
89        true
90    }
91    fn priority(&self) -> u8 {
92        65
93    }
94}
95
96impl CreateCommand {
97    fn create_server(
98        &self,
99        config: &Config,
100        ctx: &ServerContext,
101        custom_name: Option<String>,
102        custom_port: Option<u16>,
103    ) -> Result<String> {
104        let id = Uuid::new_v4().to_string();
105        let has_custom_name = custom_name.is_some();
106        let has_custom_port = custom_port.is_some();
107
108        let name = if let Some(custom_name) = custom_name {
109            validate_server_name(&custom_name)?;
110            let servers = ctx.servers.read().unwrap();
111            if servers.values().any(|s| s.name == custom_name) {
112                return Err(AppError::Validation(format!(
113                    "Server-Name '{}' bereits vergeben!",
114                    custom_name
115                )));
116            }
117            custom_name
118        } else {
119            let server_number = self.find_next_server_number(ctx);
120            format!("rush-sync-server-{:03}", server_number)
121        };
122
123        let port = if let Some(custom_port) = custom_port {
124            // Use configurable minimum port from config
125            let min_port = config.server.port_range_start.max(1024);
126            if custom_port < min_port {
127                return Err(AppError::Validation(format!(
128                    "Port must be >= {} (configured minimum: {})",
129                    min_port, config.server.port_range_start
130                )));
131            }
132
133            // Check if port is within configured range
134            if custom_port > config.server.port_range_end {
135                return Err(AppError::Validation(format!(
136                    "Port {} exceeds configured maximum: {}",
137                    custom_port, config.server.port_range_end
138                )));
139            }
140
141            let servers = ctx.servers.read().unwrap();
142            if servers.values().any(|s| s.port == custom_port) {
143                return Err(AppError::Validation(format!(
144                    "Port {} bereits verwendet!",
145                    custom_port
146                )));
147            }
148            if !self.is_port_available(custom_port) {
149                return Err(AppError::Validation(format!(
150                    "Port {} bereits belegt!",
151                    custom_port
152                )));
153            }
154            custom_port
155        } else {
156            self.find_next_available_port(config)?
157        };
158
159        // Check server count limit
160        let current_server_count = ctx.servers.read().unwrap().len();
161        if current_server_count >= config.server.max_concurrent {
162            return Err(AppError::Validation(format!(
163                "Maximum server limit reached: {}/{}. Use 'cleanup' to remove stopped servers or increase max_concurrent in config.",
164                current_server_count, config.server.max_concurrent
165            )));
166        }
167
168        let timestamp = std::time::SystemTime::now()
169            .duration_since(std::time::UNIX_EPOCH)
170            .unwrap_or_default()
171            .as_secs();
172
173        let server_info = ServerInfo {
174            id: id.clone(),
175            name: name.clone(),
176            port,
177            status: ServerStatus::Stopped,
178            created_at: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
179            created_timestamp: timestamp,
180        };
181
182        // Add to runtime context
183        ctx.servers
184            .write()
185            .unwrap()
186            .insert(id.clone(), server_info.clone());
187
188        // Persist to file (async)
189        let registry = crate::server::shared::get_persistent_registry();
190        let server_info_clone = server_info.clone();
191        tokio::spawn(async move {
192            if let Ok(persistent_servers) = registry.load_servers().await {
193                if let Err(e) = registry
194                    .add_server(persistent_servers, server_info_clone)
195                    .await
196                {
197                    log::error!("Failed to persist server: {}", e);
198                }
199            }
200        });
201
202        let success_msg = if has_custom_name || has_custom_port {
203            format!(
204                "Custom Server created: '{}' (ID: {}) on port {} [PERSISTENT] ({}/{} servers)",
205                name,
206                &id[0..8],
207                port,
208                current_server_count + 1,
209                config.server.max_concurrent
210            )
211        } else {
212            format!(
213                "Server created: '{}' (ID: {}) on port {} [PERSISTENT] ({}/{} servers)",
214                name,
215                &id[0..8],
216                port,
217                current_server_count + 1,
218                config.server.max_concurrent
219            )
220        };
221
222        Ok(success_msg)
223    }
224
225    // Updated to use config instead of context
226    fn find_next_available_port(&self, config: &Config) -> Result<u16> {
227        let ctx = crate::server::shared::get_shared_context();
228        let servers = ctx.servers.read().unwrap();
229        let mut used_ports: Vec<u16> = servers.values().map(|s| s.port).collect();
230        used_ports.sort();
231
232        let mut candidate_port = config.server.port_range_start;
233        let max_port = config.server.port_range_end;
234
235        loop {
236            if candidate_port > max_port {
237                return Err(AppError::Validation(format!(
238                    "No available ports in configured range {}-{}",
239                    config.server.port_range_start, config.server.port_range_end
240                )));
241            }
242
243            if !used_ports.contains(&candidate_port) && self.is_port_available(candidate_port) {
244                return Ok(candidate_port);
245            }
246
247            candidate_port += 1;
248        }
249    }
250
251    fn is_port_available(&self, port: u16) -> bool {
252        use std::net::TcpListener;
253        use std::time::Duration;
254
255        match TcpListener::bind(("127.0.0.1", port)) {
256            Ok(listener) => {
257                drop(listener);
258                std::thread::sleep(Duration::from_millis(10));
259                TcpListener::bind(("127.0.0.1", port)).is_ok()
260            }
261            Err(_) => false,
262        }
263    }
264
265    fn find_next_server_number(&self, ctx: &ServerContext) -> u32 {
266        let servers = ctx.servers.read().unwrap();
267        let mut existing_numbers = Vec::new();
268
269        for server in servers.values() {
270            if let Some(number_str) = server.name.strip_prefix("rush-sync-server-") {
271                if let Ok(number) = number_str.parse::<u32>() {
272                    existing_numbers.push(number);
273                }
274            }
275        }
276
277        existing_numbers.sort();
278        let mut next_number = 1;
279        for &existing in &existing_numbers {
280            if existing == next_number {
281                next_number += 1;
282            } else {
283                break;
284            }
285        }
286        next_number
287    }
288}