63 lines
2.3 KiB
Rust
63 lines
2.3 KiB
Rust
//! Loopback JSON-lines control server: one request per line, one reply per line.
|
|
//! Agents and tests drive the app through it (adapted from CADCraft, MIT OR Apache-2.0).
|
|
|
|
use std::io::{BufRead, BufReader, Write};
|
|
use std::net::{TcpListener, TcpStream};
|
|
use std::sync::mpsc::{Receiver, Sender, channel};
|
|
use std::time::Duration;
|
|
|
|
use serde_json::{Value, json};
|
|
use solvecraft_ui_egui::ControlRequest;
|
|
|
|
pub fn start(port: u16, ctx: egui::Context) -> Receiver<ControlRequest> {
|
|
let (tx, rx) = channel::<ControlRequest>();
|
|
let listener = match TcpListener::bind(("127.0.0.1", port)) {
|
|
Ok(l) => l,
|
|
Err(e) => {
|
|
eprintln!("solvecraft: control server failed to bind 127.0.0.1:{port}: {e}");
|
|
return rx;
|
|
}
|
|
};
|
|
eprintln!("solvecraft: control server listening on 127.0.0.1:{port}");
|
|
std::thread::spawn(move || {
|
|
for stream in listener.incoming().flatten() {
|
|
let tx = tx.clone();
|
|
let ctx = ctx.clone();
|
|
std::thread::spawn(move || serve(stream, tx, ctx));
|
|
}
|
|
});
|
|
rx
|
|
}
|
|
|
|
fn serve(stream: TcpStream, tx: Sender<ControlRequest>, ctx: egui::Context) {
|
|
let Ok(read) = stream.try_clone() else { return };
|
|
let mut out = stream;
|
|
for line in BufReader::new(read).lines() {
|
|
let Ok(line) = line else { break };
|
|
if line.trim().is_empty() {
|
|
continue;
|
|
}
|
|
let reply = match serde_json::from_str::<Value>(&line) {
|
|
Ok(msg) => {
|
|
let id = msg.get("id").cloned().unwrap_or(Value::Null);
|
|
let method = msg.get("method").and_then(Value::as_str).unwrap_or("").to_string();
|
|
let params = msg.get("params").cloned().unwrap_or(json!({}));
|
|
let (req, rrx) = ControlRequest::new(method, params);
|
|
if tx.send(req).is_err() {
|
|
break;
|
|
}
|
|
ctx.request_repaint();
|
|
let mut r = rrx.recv_timeout(Duration::from_secs(60)).unwrap_or_else(|_| json!({"ok": false, "error": "timeout"}));
|
|
if let Some(o) = r.as_object_mut() {
|
|
o.insert("id".into(), id);
|
|
}
|
|
r
|
|
}
|
|
Err(e) => json!({"ok": false, "error": format!("bad JSON: {e}")}),
|
|
};
|
|
if writeln!(out, "{reply}").is_err() {
|
|
break;
|
|
}
|
|
}
|
|
}
|