1use std::{env::args, path::PathBuf};
29
30use serde::{Deserialize, Serialize};
31use tokio::{
32 io::{AsyncReadExt, AsyncWriteExt},
33 net::{
34 UnixStream,
35 unix::{OwnedReadHalf, OwnedWriteHalf},
36 },
37 signal::unix::{SignalKind, signal},
38};
39use tracing::info;
40
41use crate::prelude::*;
42pub mod error;
43pub mod prelude;
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub enum BuilderEvent {
48 Exit,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub enum BuilderResponse {
55 Ack,
57}
58#[derive(Debug, Clone, Copy)]
59pub enum Action {
60 Build,
61 Run,
62}
63
64impl TryFrom<&str> for Action {
65 type Error = Error;
66
67 fn try_from(value: &str) -> Result<Self> {
68 if value == "build" {
69 return Ok(Action::Build);
70 }
71 if value == "run" {
72 return Ok(Action::Run);
73 }
74 Err(Error::InvalidAction(String::from(value)))
75 }
76}
77
78impl From<Action> for &'static str {
79 fn from(value: Action) -> Self {
80 match value {
81 Action::Build => "build",
82 Action::Run => "run",
83 }
84 }
85}
86
87impl From<Action> for String {
88 fn from(value: Action) -> Self {
89 let value: &str = value.into();
90 Self::from(value)
91 }
92}
93
94#[derive(Debug, Clone)]
99pub struct BuilderSdk {
100 board_name: String,
102 board_config_name: String,
104 config_path: String,
106 action: Action,
108}
109
110impl BuilderSdk {
111 pub async fn init<F, Fut>(event_callback: F) -> Result<Self>
134 where
135 F: Fn(Self, BuilderEvent) -> Fut + Send + Sync + 'static,
136 Fut: Future<Output = Result<()>> + Send + 'static,
137 {
138 let args: Vec<String> = std::env::args().into_iter().collect();
139 if args.len() < 6 {
140 return Err(Error::MissingArgs(6, args.len()));
141 }
142
143 let action: Action = TryFrom::<&str>::try_from(&args[1])?;
144
145 let stream = UnixStream::connect(&args[5]).await?;
146 let sdk = Self {
147 config_path: args[2].clone(),
148 board_name: args[3].clone(),
149 board_config_name: args[4].clone(),
150 action,
151 };
152 let sdk_loop = sdk.clone();
153 let mut sigint = signal(SignalKind::interrupt())?;
154 tokio::spawn(async move {
155 while sigint.recv().await.is_some() {
156 info!("SIGINT received");
157 }
158 });
159
160 tokio::spawn(async move { sdk_loop.start_event_loop(stream, event_callback).await });
161 Ok(sdk)
162 }
163 pub fn action(&self) -> Action {
165 self.action
166 }
167 pub fn config_path(&self) -> PathBuf {
169 PathBuf::from(&self.config_path)
170 }
171 pub fn board_name(&self) -> &str {
173 &self.board_name
174 }
175 pub fn board_config_name(&self) -> &str {
177 &self.board_config_name
178 }
179 fn parse_event(payload: &str) -> Result<BuilderEvent> {
181 Ok(serde_json::from_str(payload)?)
182 }
183 async fn start_event_loop<F, Fut>(self, stream: UnixStream, cb: F) -> Result<()>
185 where
186 F: Fn(Self, BuilderEvent) -> Fut + Send + Sync + 'static,
187 Fut: Future<Output = Result<()>> + Send + 'static,
188 {
189 let mut payload = String::new();
190 let (mut rx, mut tx) = stream.into_split();
191
192 loop {
193 tokio::select! {
194 read_result = rx.read_to_string(&mut payload) => {
195 match read_result {
196 Ok(0) => break,
197 Ok(n) => {
198 let event = BuilderSdk::parse_event(&payload)?;
199 info!("Received event from builder {:?}", event);
200 cb(self.clone(), event).await;
201 info!("Acking event to builder");
202 let response = serde_json::to_string(&BuilderResponse::Ack)?;
203 tx.write_all(response.as_bytes()).await;
204 tx.write_all(b"\n").await;
205 tx.flush().await;
206 }
207 Err(e) => return Err(Error::from(e)),
208 }
209 }
210
211 _ = tokio::signal::ctrl_c() => {
212 info!("Received Ctrl+C, shutting down...");
213 cb(self.clone(), BuilderEvent::Exit).await; break;
215 }
216 }
217 }
218 Ok(())
219 }
220}