118 lines
3.3 KiB
Rust
118 lines
3.3 KiB
Rust
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<Child> {
|
|
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<Child>) {
|
|
if let Some(mut c) = child.take() {
|
|
let _ = c.kill();
|
|
let _ = c.wait();
|
|
}
|
|
}
|