use std::net::{SocketAddr, TcpStream}; use std::path::{Path, PathBuf}; use std::process::{Child, Command, Stdio}; use std::time::{Duration, Instant}; pub const GUI_PORT: u16 = 8765; /// Project root containing `juc500_app.py` (parent of `juc500-app/` in dev). pub fn project_root() -> PathBuf { if let Ok(root) = std::env::var("JUC500_ROOT") { let p = PathBuf::from(root); if p.join("juc500_app.py").is_file() { return p; } } if let Ok(cwd) = std::env::current_dir() { if cwd.join("juc500_app.py").is_file() { return cwd; } let parent = cwd.parent().map(Path::to_path_buf); if let Some(ref p) = parent { if p.join("juc500_app.py").is_file() { return p.clone(); } } } if let Ok(exe) = std::env::current_exe() { let mut dir = exe.parent().map(Path::to_path_buf); for _ in 0..10 { let Some(ref d) = dir else { break }; if d.join("juc500_app.py").is_file() { return d.clone(); } if d.join("..").join("juc500_app.py").exists() { if let Ok(canon) = d.join("..").canonicalize() { if canon.join("juc500_app.py").is_file() { return canon; } } } dir = d.parent().map(Path::to_path_buf); } } PathBuf::from(".") } pub fn resolve_python(root: &Path) -> PathBuf { if cfg!(windows) { let venv = root.join(".venv").join("Scripts").join("python.exe"); if venv.is_file() { return venv; } } else { let venv = root.join(".venv").join("bin").join("python"); if venv.is_file() { return venv; } } if cfg!(windows) { PathBuf::from("python") } else { PathBuf::from("python3") } } pub fn kill_wormhole(root: &Path) { let py = resolve_python(root); let _ = Command::new(&py) .current_dir(root) .env("PYTHONPATH", root) .args([ "-c", "from juc500_xfer.wormhole_kill import kill_wormhole_processes; \ kill_wormhole_processes(force=True)", ]) .stdout(Stdio::null()) .stderr(Stdio::null()) .status(); } pub fn backend_running(port: u16) -> bool { let addr: SocketAddr = format!("127.0.0.1:{}", port).parse().unwrap(); TcpStream::connect_timeout(&addr, Duration::from_millis(200)).is_ok() } pub fn wait_backend(port: u16, timeout: Duration) -> bool { let deadline = Instant::now() + timeout; while Instant::now() < deadline { if backend_running(port) { return true; } std::thread::sleep(Duration::from_millis(200)); } false } pub fn start_backend(root: &Path) -> std::io::Result { kill_wormhole(root); let py = resolve_python(root); let app = root.join("juc500_app.py"); Command::new(&py) .current_dir(root) .env("PYTHONPATH", root) .env("JUC500_TAURI", "1") .arg(&app) .arg("--backend") .stdout(Stdio::null()) .stderr(Stdio::piped()) .spawn() } pub fn stop_backend(child: &mut Option) { if let Some(mut c) = child.take() { let _ = c.kill(); let _ = c.wait(); } }