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(name: &'static str, task: F) -> Result>> where F: FnOnce() -> Result + 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) }