rush_sync_server/commands/create/
command.rs1use 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 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 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 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 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 ctx.servers
153 .write()
154 .unwrap()
155 .insert(id.clone(), server_info.clone());
156
157 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
163 .add_server(persistent_servers, server_info_clone)
164 .await
165 {
166 log::error!("Failed to persist server: {}", e);
167 }
168 }
169 });
170
171 let success_msg = if has_custom_name || has_custom_port {
172 format!(
173 "Custom Server created: '{}' (ID: {}) on port {} [PERSISTENT] ({}/{} servers)",
174 name,
175 &id[0..8],
176 port,
177 current_server_count + 1,
178 config.server.max_concurrent
179 )
180 } else {
181 format!(
182 "Server created: '{}' (ID: {}) on port {} [PERSISTENT] ({}/{} servers)",
183 name,
184 &id[0..8],
185 port,
186 current_server_count + 1,
187 config.server.max_concurrent
188 )
189 };
190
191 Ok(success_msg)
192 }
193
194 fn find_next_available_port(&self, config: &Config) -> Result<u16> {
196 let ctx = crate::server::shared::get_shared_context();
197 let used_ports: std::collections::HashSet<u16> = {
198 let servers = ctx
199 .servers
200 .read()
201 .map_err(|_| AppError::Validation("Server-Context lock poisoned".to_string()))?;
202 servers.values().map(|s| s.port).collect()
203 };
204
205 let start_port = config.server.port_range_start;
206 let end_port = config.server.port_range_end;
207
208 if start_port >= end_port {
210 return Err(AppError::Validation(format!(
211 "Invalid port range: {} >= {}. Check config.",
212 start_port, end_port
213 )));
214 }
215
216 let max_attempts = ((end_port - start_port + 1) as usize).min(1000);
218
219 for i in 0..max_attempts {
220 let candidate_port = start_port + (i as u16);
221
222 if candidate_port > end_port {
223 break;
224 }
225
226 if !used_ports.contains(&candidate_port)
227 && crate::server::utils::port::is_port_available(candidate_port)
228 {
229 return Ok(candidate_port);
230 }
231 }
232
233 Err(AppError::Validation(format!(
234 "No available ports in range {}-{} after {} attempts",
235 start_port, end_port, max_attempts
236 )))
237 }
238
239 fn find_next_server_number(&self, ctx: &ServerContext) -> u32 {
240 let servers = ctx.servers.read().unwrap();
241 let mut existing_numbers = Vec::new();
242
243 for server in servers.values() {
244 if let Some(number_str) = server.name.strip_prefix("rss-") {
245 if let Ok(number) = number_str.parse::<u32>() {
246 existing_numbers.push(number);
247 }
248 }
249 }
250
251 existing_numbers.sort();
252 let mut next_number = 1;
253 for &existing in &existing_numbers {
254 if existing == next_number {
255 next_number += 1;
256 } else {
257 break;
258 }
259 }
260 next_number
261 }
262}