mod buffer;
mod child;
mod cli;
mod config;
mod endpoint;
mod host;
mod logging;
mod progress;
mod pump;
mod relay;
mod shutdown;
use std::{process::ExitCode, time::Duration};
use clap::Parser;
use cli::Cli;
use config::{load_config, resolve};
use logging::{bootstrap_logging, init_logging};
use tocat_api::Registry;
use tracing::{debug, error};
use crate::{progress::Progress, relay::Relay};
const TEARDOWN_GRACE: Duration = Duration::from_millis(250);
#[cfg(feature = "schema")]
const SCHEMA: &str = include_str!("../tocat.schema.json");
fn main() -> anyhow::Result<ExitCode> {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?;
let result = runtime.block_on(start());
runtime.shutdown_timeout(TEARDOWN_GRACE);
result
}
async fn start() -> anyhow::Result<ExitCode> {
let cli = Cli::parse();
let registry: Registry = tocat_plugins::native_registry();
if cli.list_plugins {
for factory in registry.iter() {
println!("{:<12} {}", factory.name(), factory.description());
}
return Ok(ExitCode::SUCCESS);
}
#[cfg(feature = "schema")]
{
if cli.dump_schema {
println!("{SCHEMA}");
return Ok(ExitCode::SUCCESS);
}
}
let cli_level = cli.verbose_level();
let initial = cli_level.unwrap_or_default();
let bootstrap = bootstrap_logging(initial);
let (mut config, source_file) = load_config(cli.config.clone(), cli.no_config)?;
config.merge_cli(&cli)?;
let level = match cli.log_level {
Some(explicit) => explicit,
None => cli_level.max(config.log_level).unwrap_or_default(),
};
config.log_level = Some(level);
if cli.dump_config {
println!("{}", toml::to_string(&config).unwrap());
return Ok(ExitCode::SUCCESS);
}
drop(bootstrap);
let _logging_guard = init_logging(&config.log, level)?;
let settings = resolve(config)?;
match &source_file {
Some(path) => debug!("using config file: {}", path.display()),
None => debug!("no config found, using defaults"),
}
let shutdown = shutdown::install();
let progress = progress::start(settings.progress, &settings.source, &settings.sink);
let relay = match Relay::new(
settings.source,
settings.sink,
settings.plugins,
registry,
settings.buffer,
progress.as_ref().map(Progress::meter),
)
.await
{
Ok(relay) => relay,
Err(e) => {
error!("Plugin setup failed: {e:#}");
finish(progress).await;
return Ok(ExitCode::FAILURE);
}
};
let outcome = relay.run(shutdown).await;
finish(progress).await;
if let Err(e) = outcome {
error!("Relay failed: {e:#}");
return Ok(ExitCode::FAILURE);
}
Ok(ExitCode::SUCCESS)
}
async fn finish(progress: Option<Progress>) {
if let Some(progress) = progress {
progress.finish().await;
}
}