use std::io::{self, Read, Write};
use std::net::{TcpListener, TcpStream};
use std::path::Path;
use std::process;
const NAK: u8 = 0x15;
const RS: u8 = 0x1e;
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 3 {
eprintln!("usage: virtuoso-daemon <host> <port>");
process::exit(1);
}
let host = &args[1];
let port: u16 = args[2].parse().unwrap_or_else(|_| {
eprintln!("invalid port: {}", args[2]);
process::exit(1);
});
let listener = TcpListener::bind(format!("{host}:{port}")).unwrap_or_else(|e| {
eprintln!("failed to bind {host}:{port}: {e}");
process::exit(1);
});
let actual_port = listener.local_addr().map(|a| a.port()).unwrap_or(port);
eprintln!("VERSION:{}", env!("CARGO_PKG_VERSION"));
eprintln!("PORT:{actual_port}");
eprintln!("[virtuoso-daemon] listening on {host}:{actual_port}");
let cb_port = actual_port + 1;
for stream in listener.incoming() {
match stream {
Ok(conn) => {
if let Err(e) = handle_connection(conn, cb_port) {
eprintln!("[virtuoso-daemon] error: {e}");
}
}
Err(e) => {
eprintln!("[virtuoso-daemon] accept error: {e}");
}
}
}
}
fn handle_connection(mut conn: TcpStream, cb_port: u16) -> io::Result<()> {
let mut req_bytes = Vec::new();
conn.read_to_end(&mut req_bytes)?;
let req: SkillRequest = serde_json::from_slice(&req_bytes)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("invalid json: {e}")))?;
let timeout = req.timeout.unwrap_or(30);
let (data_file, done_file) = callback_files(cb_port);
let _ = std::fs::remove_file(&data_file);
let _ = std::fs::remove_file(&done_file);
let stdout = io::stdout();
let mut out = stdout.lock();
out.write_all(req.skill.as_bytes())?;
out.flush()?;
let result = read_callback_file(cb_port, timeout)?;
conn.write_all(&result)?;
let _ = conn.shutdown(std::net::Shutdown::Both);
Ok(())
}
fn callback_files(cb_port: u16) -> (String, String) {
(
format!("/tmp/.ramic_cb_{cb_port}"),
format!("/tmp/.ramic_cb_{cb_port}.done"),
)
}
fn read_callback_file(cb_port: u16, timeout_secs: u64) -> io::Result<Vec<u8>> {
let (data_file, done_file) = callback_files(cb_port);
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(timeout_secs);
loop {
if std::time::Instant::now() > deadline {
return Ok(vec![
NAK, b'T', b'i', b'm', b'e', b'o', b'u', b't', b'E', b'r', b'r', b'o', b'r',
]);
}
if Path::new(&done_file).exists() {
match std::fs::read(&data_file) {
Ok(mut data) => {
let _ = std::fs::remove_file(&data_file);
let _ = std::fs::remove_file(&done_file);
if data.last() == Some(&RS) {
data.pop();
}
return Ok(data);
}
Err(_) => {
}
}
}
std::thread::sleep(std::time::Duration::from_millis(10));
}
}
#[derive(serde::Deserialize)]
struct SkillRequest {
skill: String,
timeout: Option<u64>,
}