use std::io;
use std::net::{TcpListener, TcpStream};
use std::process::Command;
use std::thread;
use std::time::{Duration, Instant};
pub fn is_port_in_use(port: u16) -> bool {
if TcpListener::bind(("127.0.0.1", port)).is_err() {
return true;
}
TcpStream::connect(("127.0.0.1", port)).is_ok()
}
pub fn find_pids_on_port(port: u16) -> Vec<u32> {
#[cfg(unix)]
{
let output = Command::new("lsof")
.args(["-ti", &format!("tcp:{}", port)])
.output();
if let Ok(out) = output {
let s = String::from_utf8_lossy(&out.stdout);
let mut pids = Vec::new();
for line in s.lines() {
if let Ok(pid) = line.trim().parse::<u32>() {
if pid > 0 && !pids.contains(&pid) {
pids.push(pid);
}
}
}
return pids;
}
}
#[cfg(windows)]
{
let output = Command::new("cmd")
.args(["/C", &format!("netstat -ano | findstr :{}", port)])
.output();
if let Ok(out) = output {
let s = String::from_utf8_lossy(&out.stdout);
let mut pids = Vec::new();
for line in s.lines() {
if let Some(last_token) = line.split_whitespace().last() {
if let Ok(pid) = last_token.parse::<u32>() {
if pid > 0 && !pids.contains(&pid) {
pids.push(pid);
}
}
}
}
return pids;
}
}
Vec::new()
}
pub fn free_port_if_occupied(port: u16) -> io::Result<Option<u32>> {
if !is_port_in_use(port) {
return Ok(None);
}
let pids = find_pids_on_port(port);
let my_pid = std::process::id();
for pid in &pids {
if *pid == my_pid {
continue;
}
println!("⚠️ Port {} is occupied by PID {}. Terminating conflicting process...", port, pid);
let _ = crate::process::operations::stop_process(*pid);
}
let start = Instant::now();
while start.elapsed() < Duration::from_secs(2) {
if !is_port_in_use(port) {
return Ok(pids.into_iter().next());
}
thread::sleep(Duration::from_millis(100));
}
Ok(pids.into_iter().next())
}
pub fn wait_for_health(
port: Option<u16>,
http_url: Option<&str>,
timeout: Duration,
interval: Duration,
) -> Result<(), String> {
let start = Instant::now();
while start.elapsed() < timeout {
if let Some(p) = port {
if TcpStream::connect(("127.0.0.1", p)).is_ok() {
return Ok(());
}
}
if let Some(url) = http_url {
if let Some(p) = extract_port_from_url(url) {
if TcpStream::connect(("127.0.0.1", p)).is_ok() {
return Ok(());
}
}
}
thread::sleep(interval);
}
Err(format!(
"Health check timed out after {}s",
timeout.as_secs()
))
}
fn extract_port_from_url(url: &str) -> Option<u16> {
let stripped = url.trim_start_matches("http://").trim_start_matches("https://");
let host_port = stripped.split('/').next()?;
let port_str = host_port.split(':').nth(1)?;
port_str.parse::<u16>().ok()
}