From bfb995563f41b86de2f0a82999c99391f7f00582 Mon Sep 17 00:00:00 2001
From: memdmp <memdmp@estrogen.zone>
Date: Sun, 18 May 2025 11:41:01 +0200
Subject: feat: initial commit

---
 src/main.rs | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 176 insertions(+)
 create mode 100644 src/main.rs

(limited to 'src')

diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..7c63c09
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,176 @@
+use std::env;
+use std::io::Error;
+use std::net::SocketAddr;
+use std::sync::LazyLock;
+use std::time::Duration;
+
+use http_body_util::{BodyExt, Empty, Full};
+use hyper::Uri;
+use hyper::body::Incoming;
+use hyper::header::HeaderValue;
+use hyper::{Request, Response, body::Bytes, server::conn::http1, service::service_fn};
+use hyper_util::rt::TokioIo;
+use serde_json::json;
+use tokio::net::TcpStream;
+use tokio::{net::TcpListener, sync::RwLock, task, time};
+
+static RAW_JSON: &'static str = include_str!("../api.json");
+static PARSED_JSON: LazyLock<RwLock<serde_json::Value>> =
+  LazyLock::new(|| RwLock::new(serde_json::from_str(RAW_JSON).expect("JSON parse error")));
+
+type StdResult<T, E> = std::result::Result<T, E>;
+type Result<T> = StdResult<T, Box<dyn std::error::Error + Send + Sync>>;
+
+async fn respond(_: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>> {
+  let str = PARSED_JSON.read().await.to_string();
+  let len = str.len();
+  let mut res = Response::new(Full::new(Bytes::from(str)));
+  res
+    .headers_mut()
+    .append("Content-Type", HeaderValue::from_static("application/json"));
+  let len = HeaderValue::from_str(&len.to_string());
+  if len.is_ok() {
+    res.headers_mut().append("Content-Length", len.unwrap());
+  }
+  Ok(res)
+}
+async fn fetch_url(url: hyper::Uri) -> Result<Response<Incoming>> {
+  if url.scheme_str() != Some("http") {
+    return Err(Box::new(Error::new(
+      std::io::ErrorKind::InvalidInput,
+      "This function only works with HTTP URIs.",
+    )));
+  }
+
+  let host = url.host().expect("uri has no host");
+  let port = url.port_u16().unwrap_or(80);
+  let addr = format!("{}:{}", host, port);
+  let stream = TcpStream::connect(addr).await?;
+  let io = TokioIo::new(stream);
+
+  let (mut sender, conn) = hyper::client::conn::http1::handshake(io).await?;
+  tokio::task::spawn(async move {
+    if let Err(err) = conn.await {
+      println!("Connection failed: {:?}", err);
+    }
+  });
+
+  let authority = url.authority().unwrap().clone();
+
+  let path = url.path();
+  let req = Request::builder()
+    .uri(path)
+    .header(hyper::header::HOST, authority.as_str())
+    .body(Empty::<Bytes>::new())?;
+
+  Ok(sender.send_request(req).await?)
+}
+fn get_request_target_uri() -> Uri {
+  env::var("REQUEST_TO")
+    .unwrap_or("http://10.0.0.77:8080/".to_string())
+    .parse::<hyper::Uri>()
+    .unwrap()
+}
+async fn get_is_open() -> Result<bool> {
+  let mut res = fetch_url(get_request_target_uri()).await?;
+
+  let mut out = "".to_string();
+  while let Some(next) = res.frame().await {
+    let frame = next?;
+    if let Some(chunk) = frame.data_ref() {
+      out = format!("{out}{}", std::str::from_utf8(chunk).unwrap());
+    }
+  }
+
+  let state = out.trim().starts_with("a");
+
+  Ok(state)
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+  {
+    let state = {
+      let mut parsed_json = PARSED_JSON.write().await;
+      if !parsed_json.is_object() {
+        panic!("api.json must have top-level object!");
+      }
+      let parsed_json_obj = parsed_json.as_object_mut().unwrap();
+      let state = parsed_json_obj.get_mut("state");
+      let state = if state.is_some() {
+        state.unwrap()
+      } else {
+        &mut json!("{}")
+      };
+      let state_obj = state.as_object_mut().unwrap();
+      state_obj.remove_entry("open");
+      state.clone()
+    };
+
+    // ensure state is present
+    let mut parsed_json = PARSED_JSON.write().await;
+    let parsed_json_obj = parsed_json.as_object_mut().unwrap();
+    parsed_json_obj.insert("state".to_string(), state);
+  }
+  task::spawn(async {
+    loop {
+      {
+        let is_open = get_is_open().await;
+
+        let mut parsed_json = PARSED_JSON.write().await;
+        let state = parsed_json.get_mut("state");
+        if state.is_none() {
+          panic!("State is none!");
+        };
+        let state = state.unwrap().as_object_mut();
+        if state.is_none() {
+          panic!("State was turned into non-object!");
+        }
+        let state = state.unwrap();
+
+        if is_open.is_ok() {
+          let is_open = is_open.unwrap();
+          state.insert("open".to_string(), serde_json::Value::Bool(is_open));
+        } else {
+          state.remove("open");
+          eprintln!("Failed to fetch open status: {:#?}", is_open.unwrap_err());
+        }
+      }
+      time::sleep(Duration::from_secs(1)).await;
+    }
+  });
+
+  let addr: SocketAddr = env::var("LISTEN_ON")
+    .unwrap_or("127.0.0.1:3000".to_string())
+    .parse()?;
+
+  // We create a TcpListener and bind it to 127.0.0.1:3000
+  let listener = TcpListener::bind(addr).await?;
+
+  println!(
+    "Listening on: LISTEN_ON={addr:#?}
+Sending fetch requests to: REQUEST_TO={:#?}",
+    get_request_target_uri()
+  );
+
+  // We start a loop to continuously accept incoming connections
+  loop {
+    let (stream, _) = listener.accept().await?;
+
+    // Use an adapter to access something implementing `tokio::io` traits as if they implement
+    // `hyper::rt` IO traits.
+    let io = TokioIo::new(stream);
+
+    // Spawn a tokio task to serve multiple connections concurrently
+    tokio::task::spawn(async move {
+      // Finally, we bind the incoming connection to our `hello` service
+      if let Err(err) = http1::Builder::new()
+        // `service_fn` converts our function in a `Service`
+        .serve_connection(io, service_fn(respond))
+        .await
+      {
+        eprintln!("Error serving connection: {:?}", err);
+      }
+    });
+  }
+}
-- 
cgit v1.2.3