adding local support

This commit is contained in:
ParkerTenBroeck 2026-03-10 18:14:50 -04:00
parent 191101591e
commit 92102e3629
5 changed files with 515 additions and 183 deletions

View file

@ -3,24 +3,30 @@ use axum::{
extract::ws::{Message, WebSocket, WebSocketUpgrade},
routing::get,
};
use futures_util::{SinkExt, StreamExt};
use futures_util::{
stream::{SplitSink, SplitStream},
};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, net::SocketAddr};
use std::{collections::HashMap, net::SocketAddr, path::PathBuf, time::Duration};
use tokio::{
io::{AsyncBufReadExt, BufReader},
io::{BufReader, Lines},
process::{Child, ChildStderr, ChildStdin, ChildStdout},
};
use tower_http::services::ServeDir;
use crate::build::TempDir;
pub mod build;
pub mod run;
pub mod local;
pub mod remote;
#[tokio::main]
async fn main() {
let app = Router::new()
.route(
"/ws",
get(|ws: WebSocketUpgrade| async move { ws.on_upgrade(ws_handler) }),
get(|ws: WebSocketUpgrade| async move { ws.on_upgrade(remote::ws_handler) }),
)
.fallback_service(ServeDir::new("ui"));
@ -32,132 +38,52 @@ async fn main() {
.unwrap();
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
struct ClientInput{
/// bitfield of 32 switches
switch: u32,
/// bitfield of 32 buttons
buttons: u32
pub enum ClientMsg {
Compile(Option<HashMap<String, String>>),
Start,
Stop,
Input {
/// bitfield of 32 switches
switch: u32,
/// bitfield of 32 buttons
buttons: u32,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum ServerMsg<'a> {
pub enum ServerMsg<'a> {
Log { stream: &'a str, line: &'a str },
Start,
Stop,
Led(u32),
Seg0(u32),
Seg1(u32),
Seg2(u32),
Seg3(u32)
Seg3(u32),
}
async fn ws_handler(socket: WebSocket) {
let (mut sender, mut receiver) = socket.split();
struct Process {
process: Child,
let files = if let Some(Ok(Message::Text(msg))) = receiver.next().await
&& let Ok(files) = serde_json::from_str::<'_, HashMap<String, String>>(&msg)
{
files
} else {
return;
};
stderr: Lines<BufReader<ChildStderr>>,
stdout: Lines<BufReader<ChildStdout>>,
stdin: ChildStdin,
}
struct Handler {
sender: SplitSink<WebSocket, Message>,
receiver: SplitStream<WebSocket>,
let artifact_dir = match build::build(files).await{
Ok(dir) => dir,
Err(err) => {
_ = sender.send(Message::Text(format!("Failed to build: {err}").into())).await;
return;
},
};
build_dir: TempDir,
src_dir: PathBuf,
let mut process = match run::run(&artifact_dir).await{
Ok(process) => process,
Err(err) => {
_ = sender.send(Message::Text(format!("Failed to run: {err}").into())).await;
return;
},
};
let mut sout = BufReader::new(process.stdout).lines();
let mut serr = BufReader::new(process.stderr).lines();
program: Option<PathBuf>,
process: Option<Process>,
let artifact_prefix = artifact_dir.to_str().unwrap_or("\0\0NOPE");
let result: Result<(), Box<dyn std::error::Error + Sync + Send>> = async {
loop{
tokio::select! {
receive = receiver.next() => {
match receive{
Some(Ok(Message::Close(_))) => break,
Some(Ok(Message::Text(msg))) => {
let input = serde_json::from_str::<'_, ClientInput>(&msg)?;
use tokio::io::AsyncWriteExt;
process.stdin.write_all(format!("btn={}\n", input.buttons).as_bytes()).await?;
process.stdin.write_all(format!("sw={}\n", input.switch).as_bytes()).await?;
},
Some(Ok(_)) => {},
Some(Err(err)) => Err(err)?,
_ => break,
}
}
out = sout.next_line() => {
match out{
Ok(Some(line)) => {
let msg = ServerMsg::Log {
stream: "stdout",
line: line.strip_prefix(artifact_prefix).unwrap_or(&line),
};
sender.send(Message::Text(serde_json::to_string(&msg)?.into())).await?;
},
Ok(None) => break,
Err(err) => {
Err(format!("Failed to read proccess sout: {err}"))?;
}
}
}
err = serr.next_line() => {
match err{
Ok(Some(line)) => {
let msg = if let Some(repr) = line.strip_prefix("led="){
ServerMsg::Led(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg0="){
ServerMsg::Seg0(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg1="){
ServerMsg::Seg1(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg2="){
ServerMsg::Seg2(repr.parse().unwrap_or(0))
}else if let Some(repr) = line.strip_prefix("seg3="){
ServerMsg::Seg3(repr.parse().unwrap_or(0))
}else{
ServerMsg::Log {
stream: "stderr",
line: line.strip_prefix(artifact_prefix).unwrap_or(&line),
}
};
sender.send(Message::Text(serde_json::to_string(&msg)?.into())).await?;
},
Ok(None) => break,
Err(err) => {
Err(format!("Failed to read proccess serr: {err}"))?
}
}
}
_ = tokio::time::sleep(std::time::Duration::from_millis(30)) => {
use tokio::io::AsyncWriteExt;
process.stdin.write_all("\n".as_bytes()).await?;
}
}
}
Ok(())
}.await;
refresh_time: Duration,
}
match result{
Ok(_) => {},
Err(err) => {
_ = sender.send(Message::Text(format!("{err}").into())).await;
},
}
}
pub type HResult<T> = Result<T, Box<dyn std::error::Error + Sync + Send>>;