use std::path::Path;
use std::process::ExitCode;
use skadoosh::audio::input::list_devices;
use skadoosh::{Config, Pipeline, SkadooshError};
use tracing_subscriber::EnvFilter;
fn main() -> ExitCode {
let config = Config::parse();
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
)
.init();
match dispatch(config) {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
print_error_chain(&err);
ExitCode::FAILURE
}
}
}
fn dispatch(config: Config) -> skadoosh::Result<()> {
config.validate()?;
if config.list_devices {
for name in list_devices()? {
println!("{name}");
}
return Ok(());
}
let selftest = config.selftest.clone();
let pipeline = Pipeline::new(config)?;
match selftest {
Some(wav) => {
let report = pipeline.run_selftest(&wav, Path::new("selftest_out.wav"))?;
println!("{report}");
Ok(())
}
None => {
let token = pipeline.shutdown_token();
let bridge = sigint::install(token.clone());
let result = pipeline.run();
bridge.done();
token.cancel();
if let Some(handle) = bridge.join {
let _ = handle.join();
}
result
}
}
}
fn print_error_chain(err: &SkadooshError) {
eprintln!("error: {err}");
let mut source = std::error::Error::source(err);
while let Some(err) = source {
eprintln!("caused by: {err}");
source = err.source();
}
}
mod sigint {
use std::thread::JoinHandle;
use tokio_util::sync::CancellationToken;
pub struct SigintBridge {
done: CancellationToken,
pub join: Option<JoinHandle<()>>,
}
impl SigintBridge {
pub fn done(&self) {
self.done.cancel();
}
}
pub fn install(token: CancellationToken) -> SigintBridge {
let done = CancellationToken::new();
let thread_done = done.clone();
let join = std::thread::Builder::new()
.name("skadoosh-sigint".to_string())
.spawn(move || {
let runtime = match tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
{
Ok(runtime) => runtime,
Err(err) => {
tracing::warn!(%err, "failed to build SIGINT runtime; ctrlc will kill the process");
return;
}
};
runtime.block_on(async move {
tokio::select! {
first = tokio::signal::ctrl_c() => {
if first.is_err() {
tracing::warn!("failed to listen for SIGINT; ctrlc will kill the process");
return;
}
tracing::info!("SIGINT received; shutting down (press ctrl-c again to force)");
token.cancel();
tokio::select! {
_ = tokio::signal::ctrl_c() => std::process::exit(128 + 2),
_ = thread_done.cancelled() => {}
}
}
_ = token.cancelled() => {}
_ = thread_done.cancelled() => {}
}
});
})
.map_err(|err| {
tracing::warn!(%err, "failed to spawn SIGINT bridge thread");
err
})
.ok();
SigintBridge { done, join }
}
}