121 lines
3.8 KiB
Rust
121 lines
3.8 KiB
Rust
use std::{
|
|
fs::{create_dir_all, File},
|
|
io::Write as _,
|
|
path::Path,
|
|
process::exit,
|
|
sync::Arc,
|
|
time::Duration,
|
|
};
|
|
|
|
use flurry::{
|
|
config::{GRID_LENGTH, HOST, IMAGE_SAVE_INTERVAL, JPEG_UPDATE_INTERVAL},
|
|
flutclient::{FlutClient, ParserTypes},
|
|
grid::{self, Flut},
|
|
webapi::WebApiContext,
|
|
AsyncResult,
|
|
};
|
|
use futures::never::Never;
|
|
use tokio::{net::TcpListener, time::interval, try_join};
|
|
use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _};
|
|
|
|
/// This function starts a timer that saves the current grid state every `duration`.
|
|
/// These images may then be used for moderation or timelapses
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// This function will return an error if it is unable to create or write to the file for the image
|
|
async fn save_image_frames(
|
|
grids: Arc<[grid::Flut<u32>; GRID_LENGTH]>,
|
|
duration: Duration,
|
|
) -> AsyncResult<Never> {
|
|
let mut timer = interval(duration);
|
|
let base_dir = Path::new("./recordings");
|
|
create_dir_all(base_dir)?;
|
|
loop {
|
|
timer.tick().await;
|
|
for grid in grids.as_ref() {
|
|
let p = base_dir.join(format!(
|
|
"{}",
|
|
chrono::Local::now().format("%Y-%m-%d_%H-%M-%S.jpg")
|
|
));
|
|
let mut file_writer = File::create(p)?;
|
|
|
|
file_writer.write_all(&grid.read_jpg_buffer())?;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Handle connections made to the socket, keeps a vec of the currently active connections,
|
|
/// uses timeout to loop through them and clean them up to stop a memory leak while not throwing
|
|
/// everything away
|
|
async fn handle_flut(
|
|
flut_listener: TcpListener,
|
|
grids: Arc<[grid::Flut<u32>]>,
|
|
) -> AsyncResult<Never> {
|
|
let mut handles = Vec::new();
|
|
loop {
|
|
let (mut socket, _) = flut_listener.accept().await?;
|
|
let grids = grids.clone();
|
|
handles.push(tokio::spawn(async move {
|
|
let (reader, writer) = socket.split();
|
|
let mut connection = FlutClient::new(reader, writer, grids);
|
|
let resp = connection.process_socket().await;
|
|
match resp {
|
|
Ok(()) => Ok(()),
|
|
Err(err) => Err(err),
|
|
}
|
|
}))
|
|
}
|
|
}
|
|
|
|
async fn jpeg_update_loop(grids: Arc<[Flut<u32>]>) -> AsyncResult<Never> {
|
|
let mut interval = interval(JPEG_UPDATE_INTERVAL);
|
|
loop {
|
|
interval.tick().await;
|
|
for grid in grids.as_ref() {
|
|
grid.update_jpg_buffer();
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::main]
|
|
#[allow(clippy::needless_return)]
|
|
async fn main() {
|
|
// diagnostics
|
|
tracing_subscriber::registry()
|
|
.with(
|
|
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
|
format!("{}=info,tower_http=info", env!("CARGO_CRATE_NAME")).into()
|
|
}),
|
|
)
|
|
.with(tracing_subscriber::fmt::layer())
|
|
.init();
|
|
|
|
let grids: Arc<[Flut<u32>; GRID_LENGTH]> = [grid::Flut::init(800, 600, 0xff_00_ff_ff)].into();
|
|
tracing::trace!("created grids");
|
|
|
|
ParserTypes::announce();
|
|
|
|
let Ok(flut_listener) = TcpListener::bind(HOST).await else {
|
|
tracing::error!(
|
|
"Was unable to bind to {HOST}, please check if a different process is bound"
|
|
);
|
|
exit(1);
|
|
};
|
|
tracing::info!("Started TCP listener on {HOST}");
|
|
|
|
let snapshots = tokio::spawn(save_image_frames(grids.clone(), IMAGE_SAVE_INTERVAL));
|
|
let pixelflut_server = tokio::spawn(handle_flut(flut_listener, grids.clone()));
|
|
let jpeg_update_loop = tokio::spawn(jpeg_update_loop(grids.clone()));
|
|
let website = tokio::spawn(flurry::webapi::serve(WebApiContext {
|
|
grids: grids.clone(),
|
|
}));
|
|
|
|
let res = try_join! {
|
|
snapshots,
|
|
pixelflut_server,
|
|
jpeg_update_loop,
|
|
website,
|
|
};
|
|
tracing::error!("something went wrong {:?}", res);
|
|
}
|