mod app;
mod audio;
mod config;
mod error;
mod mpris;
mod subsonic;
mod ui;
use clap::Parser;
use crossterm::{
cursor::Show,
event::DisableMouseCapture,
execute,
terminal::{disable_raw_mode, LeaveAlternateScreen},
};
use std::fs::{self, File};
use std::path::PathBuf;
use tracing::info;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
use crate::app::App;
use crate::config::paths::config_dir;
use crate::config::Config;
#[derive(Parser, Debug)]
#[command(name = "ferrosonic")]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(short, long, value_name = "FILE")]
config: Option<PathBuf>,
#[arg(short, long)]
verbose: bool,
}
fn init_logging(verbose: bool) -> Option<tracing_appender::non_blocking::WorkerGuard> {
let log_dir = config_dir().unwrap_or_else(|| PathBuf::from("/tmp"));
if let Err(e) = fs::create_dir_all(&log_dir) {
eprintln!("Warning: Could not create log directory: {}", e);
return None;
}
let log_file = log_dir.join("ferrosonic.log");
let file = match File::create(&log_file) {
Ok(f) => f,
Err(e) => {
eprintln!("Warning: Could not create log file: {}", e);
return None;
}
};
let (non_blocking, guard) = tracing_appender::non_blocking(file);
let filter = if verbose {
EnvFilter::new("ferrosonic=debug")
} else {
EnvFilter::new("ferrosonic=info")
};
tracing_subscriber::registry()
.with(filter)
.with(
fmt::layer()
.with_writer(non_blocking)
.with_ansi(false)
.with_target(false),
)
.init();
if verbose {
eprintln!("Logging to: {}", log_file.display());
}
Some(guard)
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();
let _log_guard = init_logging(args.verbose);
info!("Termsonic starting...");
let config = match args.config {
Some(path) => {
info!("Loading config from {}", path.display());
Config::load_from_file(&path)?
}
None => {
info!("Loading default config");
Config::load_default().unwrap_or_else(|e| {
info!("No config found ({}), using defaults", e);
Config::new()
})
}
};
info!(
"Server: {}",
if config.base_url.is_empty() {
"(not configured)"
} else {
&config.base_url
}
);
let original_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let _ = disable_raw_mode();
let _ = execute!(std::io::stdout(), LeaveAlternateScreen, DisableMouseCapture, Show);
original_hook(info);
}));
let mut app = App::new(config);
if let Err(e) = app.run().await {
tracing::error!("Application error: {}", e);
return Err(e.into());
}
info!("Termsonic exiting...");
Ok(())
}