use crate::runtime::Result;
use once_cell::sync::OnceCell;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use sysinfo::System;
static ATTACHED_TO: OnceCell<RwLock<String>> = OnceCell::new();
fn get_attached() -> &'static RwLock<String> {
ATTACHED_TO.get_or_init(|| RwLock::new(String::new()))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttachConfig {
pub protocol: String,
pub addr: String,
pub pid: i32,
pub namespace: String,
}
const ATTACH_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SocketState {
Listening,
Established,
Other,
}
#[derive(Debug, Clone)]
pub struct SockTabEntry {
pub local_address: String,
pub robot_name: String,
}
pub async fn attach(namespace: &str, listen_addr: &str) -> Result<()> {
let grpc_addr = get_rpc_addr().await?;
if grpc_addr.is_empty() {
return Err(crate::runtime::RobomotionError::Variable(
"Empty gRPC address".to_string(),
));
}
let config = AttachConfig {
protocol: "grpc".to_string(),
addr: listen_addr.to_string(),
pid: std::process::id() as i32,
namespace: namespace.to_string(),
};
let config_data = serde_json::to_vec(&config)?;
tracing::info!("Attaching to {}", grpc_addr);
*get_attached().write() = grpc_addr.clone();
Ok(())
}
pub async fn detach(namespace: &str) -> Result<()> {
let attached = get_attached().read().clone();
if attached.is_empty() {
return Err(crate::runtime::RobomotionError::Variable(
"Not attached to any robot".to_string(),
));
}
Ok(())
}
async fn get_rpc_addr() -> Result<String> {
let tabs = get_netstat_ports(SocketState::Listening, "robomotion-runner").await?;
let tabs = filter_tabs(tabs).await?;
match tabs.len() {
0 => Ok(String::new()),
1 => Ok(tabs[0].local_address.clone()),
_ => select_tab(&tabs).await,
}
}
async fn get_netstat_ports(
state: SocketState,
process_name: &str,
) -> Result<Vec<SockTabEntry>> {
let mut entries = Vec::new();
let mut system = System::new_all();
system.refresh_all();
for (_pid, process) in system.processes() {
let proc_name = process.name().to_string_lossy();
if proc_name.contains(process_name) {
for port in 40000..50000 {
let addr = format!("127.0.0.1:{}", port);
if let Ok(_) = std::net::TcpStream::connect_timeout(
&addr.parse().unwrap(),
Duration::from_millis(100),
) {
entries.push(SockTabEntry {
local_address: addr,
robot_name: String::new(),
});
break;
}
}
}
}
Ok(entries)
}
async fn filter_tabs(tabs: Vec<SockTabEntry>) -> Result<Vec<SockTabEntry>> {
let mut filtered = Vec::new();
for mut tab in tabs {
if let Ok(name) = get_robot_name(&tab.local_address).await {
tab.robot_name = name;
filtered.push(tab);
}
}
Ok(filtered)
}
async fn get_robot_name(addr: &str) -> Result<String> {
Ok("Unknown Robot".to_string())
}
async fn select_tab(tabs: &[SockTabEntry]) -> Result<String> {
let count = tabs.len();
println!("\nFound {} robots running on the machine:", count);
for (i, tab) in tabs.iter().enumerate() {
println!("{}) {}", i + 1, tab.robot_name);
}
print!("Please select a robot to attach (1-{}): ", count);
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let selected: usize = input.trim().parse().unwrap_or(0);
if selected > 0 && selected <= count {
Ok(tabs[selected - 1].local_address.clone())
} else {
Err(crate::runtime::RobomotionError::Variable(
"Invalid selection".to_string(),
))
}
}