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::port::{find_next_available_port, is_port_available};
6use crate::server::utils::validation::validate_server_name;
7use std::future::Future;
8use std::pin::Pin;
9use uuid::Uuid;
10
11#[derive(Debug, Default)]
12pub struct CreateCommand;
13
14impl CreateCommand {
15 pub fn new() -> Self {
16 Self
17 }
18}
19
20impl Command for CreateCommand {
21 fn name(&self) -> &'static str {
22 "create"
23 }
24 fn description(&self) -> &'static str {
25 "Create a new web server"
26 }
27 fn matches(&self, command: &str) -> bool {
28 command.trim().to_lowercase().starts_with("create")
29 }
30
31 fn execute_sync(&self, args: &[&str]) -> Result<String> {
32 let ctx = crate::server::shared::get_shared_context();
33
34 match args.len() {
35 0 => self.create_server(ctx, None, None),
36 1 => {
37 if let Ok(port) = args[0].parse::<u16>() {
38 self.create_server(ctx, None, Some(port))
39 } else {
40 self.create_server(ctx, Some(args[0].to_string()), None)
41 }
42 }
43 2 => match args[1].parse::<u16>() {
44 Ok(port) => self.create_server(ctx, Some(args[0].to_string()), Some(port)),
45 Err(_) => Err(AppError::Validation("Invalid port".to_string())),
46 },
47 _ => Err(AppError::Validation("Too many parameters".to_string())),
48 }
49 }
50
51 fn execute_async<'a>(
52 &'a self,
53 args: &'a [&'a str],
54 ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'a>> {
55 Box::pin(async move { self.execute_sync(args) })
56 }
57
58 fn supports_async(&self) -> bool {
59 true
60 }
61 fn priority(&self) -> u8 {
62 65
63 }
64}
65
66impl CreateCommand {
67 fn create_server(
68 &self,
69 ctx: &ServerContext,
70 custom_name: Option<String>,
71 custom_port: Option<u16>,
72 ) -> Result<String> {
73 let id = Uuid::new_v4().to_string();
74 let has_custom_name = custom_name.is_some();
75 let has_custom_port = custom_port.is_some();
76
77 let name = if let Some(custom_name) = custom_name {
78 validate_server_name(&custom_name)?;
79 let servers = ctx.servers.read().unwrap();
80 if servers.values().any(|s| s.name == custom_name) {
81 return Err(AppError::Validation(format!(
82 "Server-Name '{}' bereits vergeben!",
83 custom_name
84 )));
85 }
86 custom_name
87 } else {
88 let server_number = self.find_next_server_number(ctx);
89 format!("rush-sync-server-{:03}", server_number)
90 };
91
92 let port = if let Some(custom_port) = custom_port {
93 if custom_port < 1024 {
94 return Err(AppError::Validation("Port muss >= 1024 sein!".to_string()));
95 }
96 let servers = ctx.servers.read().unwrap();
97 if servers.values().any(|s| s.port == custom_port) {
98 return Err(AppError::Validation(format!(
99 "Port {} bereits verwendet!",
100 custom_port
101 )));
102 }
103 if !is_port_available(custom_port) {
104 return Err(AppError::Validation(format!(
105 "Port {} bereits belegt!",
106 custom_port
107 )));
108 }
109 custom_port
110 } else {
111 find_next_available_port(ctx)?
112 };
113
114 let timestamp = std::time::SystemTime::now()
115 .duration_since(std::time::UNIX_EPOCH)
116 .unwrap_or_default()
117 .as_secs();
118
119 let server_info = ServerInfo {
120 id: id.clone(),
121 name: name.clone(),
122 port,
123 status: ServerStatus::Stopped,
124 created_at: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
125 created_timestamp: timestamp,
126 };
127
128 ctx.servers.write().unwrap().insert(id.clone(), server_info);
129
130 let success_msg = if has_custom_name || has_custom_port {
131 format!(
132 "Custom Server erstellt: '{}' (ID: {}) auf Port {}",
133 name,
134 &id[0..8],
135 port
136 )
137 } else {
138 format!(
139 "Server erstellt: '{}' (ID: {}) auf Port {}",
140 name,
141 &id[0..8],
142 port
143 )
144 };
145
146 Ok(success_msg)
147 }
148
149 fn find_next_server_number(&self, ctx: &ServerContext) -> u32 {
150 let servers = ctx.servers.read().unwrap();
151 let mut existing_numbers = Vec::new();
152
153 for server in servers.values() {
154 if let Some(number_str) = server.name.strip_prefix("rush-sync-server-") {
155 if let Ok(number) = number_str.parse::<u32>() {
156 existing_numbers.push(number);
157 }
158 }
159 }
160
161 existing_numbers.sort();
162 let mut next_number = 1;
163 for &existing in &existing_numbers {
164 if existing == next_number {
165 next_number += 1;
166 } else {
167 break;
168 }
169 }
170 next_number
171 }
172}