split up main.rs
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Request {
|
||||
pub id: String,
|
||||
pub action: String,
|
||||
#[serde(flatten)]
|
||||
pub extra: Value,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
pub struct Response {
|
||||
pub success: bool,
|
||||
pub data: Option<Value>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum Connection {
|
||||
#[cfg(unix)]
|
||||
Unix(UnixStream),
|
||||
Tcp(TcpStream),
|
||||
}
|
||||
|
||||
impl Read for Connection {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
match self {
|
||||
#[cfg(unix)]
|
||||
Connection::Unix(s) => s.read(buf),
|
||||
Connection::Tcp(s) => s.read(buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Connection {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
match self {
|
||||
#[cfg(unix)]
|
||||
Connection::Unix(s) => s.write(buf),
|
||||
Connection::Tcp(s) => s.write(buf),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
match self {
|
||||
#[cfg(unix)]
|
||||
Connection::Unix(s) => s.flush(),
|
||||
Connection::Tcp(s) => s.flush(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn set_read_timeout(&self, dur: Option<Duration>) -> std::io::Result<()> {
|
||||
match self {
|
||||
#[cfg(unix)]
|
||||
Connection::Unix(s) => s.set_read_timeout(dur),
|
||||
Connection::Tcp(s) => s.set_read_timeout(dur),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_write_timeout(&self, dur: Option<Duration>) -> std::io::Result<()> {
|
||||
match self {
|
||||
#[cfg(unix)]
|
||||
Connection::Unix(s) => s.set_write_timeout(dur),
|
||||
Connection::Tcp(s) => s.set_write_timeout(dur),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_socket_path(session: &str) -> PathBuf {
|
||||
let tmp = env::temp_dir();
|
||||
tmp.join(format!("agent-browser-{}.sock", session))
|
||||
}
|
||||
|
||||
fn get_pid_path(session: &str) -> PathBuf {
|
||||
let tmp = env::temp_dir();
|
||||
tmp.join(format!("agent-browser-{}.pid", session))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_port_path(session: &str) -> PathBuf {
|
||||
let tmp = env::temp_dir();
|
||||
tmp.join(format!("agent-browser-{}.port", session))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_port_for_session(session: &str) -> u16 {
|
||||
let mut hash: i32 = 0;
|
||||
for c in session.chars() {
|
||||
hash = ((hash << 5).wrapping_sub(hash)).wrapping_add(c as i32);
|
||||
}
|
||||
49152 + ((hash.abs() as u16) % 16383)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn is_daemon_running(session: &str) -> bool {
|
||||
let pid_path = get_pid_path(session);
|
||||
if !pid_path.exists() {
|
||||
return false;
|
||||
}
|
||||
if let Ok(pid_str) = fs::read_to_string(&pid_path) {
|
||||
if let Ok(pid) = pid_str.trim().parse::<i32>() {
|
||||
unsafe {
|
||||
return libc::kill(pid, 0) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_daemon_running(session: &str) -> bool {
|
||||
let pid_path = get_pid_path(session);
|
||||
if !pid_path.exists() {
|
||||
return false;
|
||||
}
|
||||
let port = get_port_for_session(session);
|
||||
TcpStream::connect_timeout(
|
||||
&format!("127.0.0.1:{}", port).parse().unwrap(),
|
||||
Duration::from_millis(100),
|
||||
)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn daemon_ready(session: &str) -> bool {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
get_socket_path(session).exists()
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let port = get_port_for_session(session);
|
||||
TcpStream::connect_timeout(
|
||||
&format!("127.0.0.1:{}", port).parse().unwrap(),
|
||||
Duration::from_millis(50),
|
||||
)
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensure_daemon(session: &str, headed: bool) -> Result<(), String> {
|
||||
if is_daemon_running(session) && daemon_ready(session) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let exe_path = env::current_exe().map_err(|e| e.to_string())?;
|
||||
let exe_dir = exe_path.parent().unwrap();
|
||||
|
||||
let daemon_paths = [
|
||||
exe_dir.join("daemon.js"),
|
||||
exe_dir.join("../dist/daemon.js"),
|
||||
PathBuf::from("dist/daemon.js"),
|
||||
];
|
||||
|
||||
let daemon_path = daemon_paths
|
||||
.iter()
|
||||
.find(|p| p.exists())
|
||||
.ok_or("Daemon not found. Run from project directory or ensure daemon.js is alongside binary.")?;
|
||||
|
||||
let mut cmd = Command::new("node");
|
||||
cmd.arg(daemon_path)
|
||||
.env("AGENT_BROWSER_DAEMON", "1")
|
||||
.env("AGENT_BROWSER_SESSION", session);
|
||||
|
||||
if headed {
|
||||
cmd.env("AGENT_BROWSER_HEADED", "1");
|
||||
}
|
||||
|
||||
cmd.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.map_err(|e| format!("Failed to start daemon: {}", e))?;
|
||||
|
||||
for _ in 0..50 {
|
||||
if daemon_ready(session) {
|
||||
return Ok(());
|
||||
}
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
|
||||
Err("Daemon failed to start".to_string())
|
||||
}
|
||||
|
||||
fn connect(session: &str) -> Result<Connection, String> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let socket_path = get_socket_path(session);
|
||||
UnixStream::connect(&socket_path)
|
||||
.map(Connection::Unix)
|
||||
.map_err(|e| format!("Failed to connect: {}", e))
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let port = get_port_for_session(session);
|
||||
TcpStream::connect(format!("127.0.0.1:{}", port))
|
||||
.map(Connection::Tcp)
|
||||
.map_err(|e| format!("Failed to connect: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_command(cmd: Value, session: &str) -> Result<Response, String> {
|
||||
let mut stream = connect(session)?;
|
||||
|
||||
stream.set_read_timeout(Some(Duration::from_secs(30))).ok();
|
||||
stream.set_write_timeout(Some(Duration::from_secs(5))).ok();
|
||||
|
||||
let mut json_str = serde_json::to_string(&cmd).map_err(|e| e.to_string())?;
|
||||
json_str.push('\n');
|
||||
|
||||
stream
|
||||
.write_all(json_str.as_bytes())
|
||||
.map_err(|e| format!("Failed to send: {}", e))?;
|
||||
|
||||
let mut reader = BufReader::new(stream);
|
||||
let mut response_line = String::new();
|
||||
reader
|
||||
.read_line(&mut response_line)
|
||||
.map_err(|e| format!("Failed to read: {}", e))?;
|
||||
|
||||
serde_json::from_str(&response_line).map_err(|e| format!("Invalid response: {}", e))
|
||||
}
|
||||
Reference in New Issue
Block a user