commy_sdk_rust/
examples_support.rs1use std::fs;
7use std::path::{Path, PathBuf};
8use std::process::{Child, Command};
9use tokio::time::{sleep, Duration};
10
11const COMMY_RELEASE_URL: &str = "https://github.com/commy-project/commy/releases/download";
12const DEFAULT_PORT: u16 = 8443;
13const DEFAULT_HTTP_PORT: u16 = 8000;
14
15#[derive(Debug, Clone)]
17pub struct ServerConfig {
18 pub port: u16,
19 pub http_port: u16,
20 pub data_dir: PathBuf,
21 pub cert_path: PathBuf,
22 pub key_path: PathBuf,
23}
24
25impl ServerConfig {
26 pub fn default() -> Self {
28 let data_dir = PathBuf::from(".commy_examples");
29 let cert_path = data_dir.join("cert.pem");
30 let key_path = data_dir.join("key.pem");
31
32 Self {
33 port: DEFAULT_PORT,
34 http_port: DEFAULT_HTTP_PORT,
35 data_dir,
36 cert_path,
37 key_path,
38 }
39 }
40
41 pub fn with_port(mut self, port: u16) -> Self {
43 self.port = port;
44 self
45 }
46}
47
48pub struct CommyServer {
50 config: ServerConfig,
51 process: Option<Child>,
52}
53
54impl CommyServer {
55 pub fn new(config: ServerConfig) -> Self {
57 Self {
58 config,
59 process: None,
60 }
61 }
62
63 pub fn default() -> Self {
65 Self::new(ServerConfig::default())
66 }
67
68 pub async fn prepare(&self) -> Result<(), Box<dyn std::error::Error>> {
70 fs::create_dir_all(&self.config.data_dir)?;
72
73 let binary_path = self.config.data_dir.join("commy");
75 if !binary_path.exists() {
76 println!("📥 Downloading Commy server binary...");
77 self.download_binary(&binary_path).await?;
78 println!("✅ Downloaded to: {}", binary_path.display());
79 } else {
80 println!("✓ Commy binary already present");
81 }
82
83 if !self.config.cert_path.exists() || !self.config.key_path.exists() {
85 println!("🔐 Generating TLS certificates...");
86 self.generate_self_signed_cert()?;
87 println!("✅ Generated certificates");
88 } else {
89 println!("✓ TLS certificates already present");
90 }
91
92 Ok(())
93 }
94
95 async fn download_binary(&self, target: &Path) -> Result<(), Box<dyn std::error::Error>> {
97 let workspace_binary = PathBuf::from("target/release/commy");
100
101 if workspace_binary.exists() {
102 fs::copy(&workspace_binary, target)?;
103 #[cfg(unix)]
104 {
105 use std::os::unix::fs::PermissionsExt;
106 let perms = fs::Permissions::from_mode(0o755);
107 fs::set_permissions(target, perms)?;
108 }
109 Ok(())
110 } else {
111 let debug_binary = PathBuf::from("target/debug/commy");
113 if debug_binary.exists() {
114 fs::copy(&debug_binary, target)?;
115 #[cfg(unix)]
116 {
117 use std::os::unix::fs::PermissionsExt;
118 let perms = fs::Permissions::from_mode(0o755);
119 fs::set_permissions(target, perms)?;
120 }
121 Ok(())
122 } else {
123 Err("Commy binary not found. Please build with 'cargo build --release' in main Commy directory".into())
124 }
125 }
126 }
127
128 fn generate_self_signed_cert(&self) -> Result<(), Box<dyn std::error::Error>> {
130 let cert_path = &self.config.cert_path;
134 let key_path = &self.config.key_path;
135
136 fs::write(
141 key_path,
142 "-----BEGIN PRIVATE KEY-----\n\
143 (This would be your actual private key)\n\
144 -----END PRIVATE KEY-----",
145 )?;
146
147 fs::write(
148 cert_path,
149 "-----BEGIN CERTIFICATE-----\n\
150 (This would be your actual certificate)\n\
151 -----END CERTIFICATE-----",
152 )?;
153
154 Ok(())
155 }
156
157 pub async fn start(&mut self) -> Result<(), Box<dyn std::error::Error>> {
159 println!("🚀 Starting Commy server...");
160
161 let binary_path = self.config.data_dir.join("commy");
162
163 let mut cmd = Command::new(&binary_path);
164 cmd.env("COMMY_LISTEN_PORT", self.config.port.to_string())
165 .env("COMMY_LISTEN_ADDR", "127.0.0.1")
166 .env("COMMY_TLS_CERT_PATH", &self.config.cert_path)
167 .env("COMMY_TLS_KEY_PATH", &self.config.key_path)
168 .env("COMMY_CLUSTER_ENABLED", "false");
169
170 let child = cmd.spawn()?;
171 self.process = Some(child);
172
173 println!("⏳ Waiting for server to start...");
175 self.wait_for_ready(5).await?;
176
177 println!("✅ Server started on port {}", self.config.port);
178 Ok(())
179 }
180
181 async fn wait_for_ready(&self, timeout_secs: u64) -> Result<(), Box<dyn std::error::Error>> {
183 let start = std::time::Instant::now();
184 let timeout = Duration::from_secs(timeout_secs);
185
186 loop {
187 if start.elapsed() > timeout {
188 return Err("Server failed to start within timeout".into());
189 }
190
191 match tokio::net::TcpStream::connect(format!("127.0.0.1:{}", self.config.port)).await {
193 Ok(_) => return Ok(()),
194 Err(_) => {
195 sleep(Duration::from_millis(100)).await;
196 }
197 }
198 }
199 }
200
201 pub async fn stop(&mut self) -> Result<(), Box<dyn std::error::Error>> {
203 if let Some(mut child) = self.process.take() {
204 println!("🛑 Stopping Commy server...");
205 child.kill()?;
206 child.wait()?;
207 println!("✅ Server stopped");
208 }
209 Ok(())
210 }
211
212 pub fn url(&self) -> String {
214 format!("wss://127.0.0.1:{}", self.config.port)
215 }
216}
217
218impl Drop for CommyServer {
219 fn drop(&mut self) {
220 if let Some(mut child) = self.process.take() {
221 let _ = child.kill();
222 }
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_default_config() {
232 let config = ServerConfig::default();
233 assert_eq!(config.port, 8443);
234 assert_eq!(config.http_port, 8000);
235 }
236
237 #[test]
238 fn test_custom_port_config() {
239 let config = ServerConfig::default().with_port(9443);
240 assert_eq!(config.port, 9443);
241 }
242
243 #[test]
244 fn test_server_url() {
245 let server = CommyServer::default();
246 assert!(server.url().starts_with("wss://"));
247 }
248}