generated from lucas/rust-template
101 lines
3.3 KiB
Rust
101 lines
3.3 KiB
Rust
use std::fs;
|
|
use std::thread::{self, JoinHandle};
|
|
|
|
use color_eyre::eyre::{eyre, Context as _};
|
|
use color_eyre::Result;
|
|
use tracing_error::ErrorLayer;
|
|
use tracing_subscriber::layer::SubscriberExt as _;
|
|
use tracing_subscriber::util::SubscriberInitExt as _;
|
|
use tracing_subscriber::{fmt, EnvFilter};
|
|
|
|
pub(crate) mod types;
|
|
mod worker {
|
|
mod api;
|
|
mod lua;
|
|
mod sender;
|
|
mod server;
|
|
|
|
pub use api::worker as api;
|
|
pub use lua::worker as lua;
|
|
pub use sender::worker as sender;
|
|
pub use server::worker as server;
|
|
}
|
|
|
|
pub(crate) static APP_USER_AGENT: &str =
|
|
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
|
|
|
|
fn spawn<T, F>(name: &'static str, task: F) -> Result<JoinHandle<Result<T>>>
|
|
where
|
|
F: FnOnce() -> Result<T> + Send + 'static,
|
|
T: Send + 'static,
|
|
{
|
|
thread::Builder::new()
|
|
.name(name.to_string())
|
|
.spawn(move || {
|
|
let res = task();
|
|
|
|
if let Err(err) = &res {
|
|
tracing::error!("Thread '{}' errored: {:?}", name, err);
|
|
}
|
|
|
|
res
|
|
})
|
|
.wrap_err_with(|| format!("Failed to create thread '{}'", name))
|
|
}
|
|
|
|
// TODO: Don't put the entire app into a Tokio runtime.
|
|
// Instead, run single-threaded runtimes off of those threads where I need
|
|
// Tokio.
|
|
// - Axum server
|
|
// - API query scheduler
|
|
// - Ntfy sender
|
|
// TODO: Find a way to communicate between them, in a way that works for both the non-Tokio
|
|
// Lua thread and the various other threads.
|
|
fn main() -> Result<()> {
|
|
color_eyre::install()?;
|
|
tracing_subscriber::registry()
|
|
.with(EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()))
|
|
.with(fmt::layer().compact())
|
|
.with(ErrorLayer::new(fmt::format::Pretty::default()))
|
|
.init();
|
|
|
|
// TODO: Create a separate, fixed thread to run Lua on
|
|
// TODO: Create a channel to send events to Lua
|
|
// TODO: Create another thread that periodically checks
|
|
// service APIs. The Lua thread should be able to send
|
|
// configurations here
|
|
// TODO: Create another thread that handles sending data to
|
|
// Ntfy. Most importantly the Lua thread needs to send events here.
|
|
// Could be consolidated with the service API thread, since it's all HTTP requests.
|
|
|
|
let config_path = std::env::var("CONFIG_PATH").wrap_err("Missing variable 'CONFIG_PATH'")?;
|
|
|
|
let (ntfy_tx, ntfy_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
let (event_tx, event_rx) = std::sync::mpsc::channel();
|
|
// A channel that lets other threads, mostly the Lua code, register
|
|
// a task with the API fetcher.
|
|
let (api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
|
|
{
|
|
let code = fs::read_to_string(&config_path)
|
|
.wrap_err_with(|| format!("Failed to read config from '{}'", config_path))?;
|
|
|
|
spawn("lua", move || worker::lua(code, event_rx, api_tx, ntfy_tx))?;
|
|
}
|
|
|
|
{
|
|
let event_tx = event_tx.clone();
|
|
spawn("api", move || worker::api(api_rx, event_tx))?;
|
|
}
|
|
|
|
{
|
|
let event_tx = event_tx.clone();
|
|
spawn("sender", move || worker::sender(event_tx, ntfy_rx))?;
|
|
}
|
|
|
|
let server_worker = spawn("server", move || worker::server(event_tx))?;
|
|
server_worker
|
|
.join()
|
|
.map_err(|err| eyre!("Thread 'server' panicked: {:?}", err))
|
|
.and_then(|res| res)
|
|
}
|