panorama/src/main.rs

122 lines
3.4 KiB
Rust

use std::path::{Path, PathBuf};
use std::thread;
use anyhow::Result;
use fern::colors::{Color, ColoredLevelConfig};
use futures::future::TryFutureExt;
use panorama::{config::spawn_config_watcher_system, mail, report_err, ui};
use structopt::StructOpt;
use tokio::{
runtime::{Builder as RuntimeBuilder, Runtime},
sync::mpsc,
task::LocalSet,
};
use xdg::BaseDirectories;
#[derive(Debug, StructOpt)]
#[structopt(author, about)]
struct Opt {
/// The path to the log file. By default, does not log.
#[structopt(long = "log-file")]
log_file: Option<PathBuf>,
/// Run this application headlessly
#[structopt(long = "headless")]
headless: bool,
/// Don't watch the config file for changes. (NYI)
// TODO: implement this or decide if it's useless
#[structopt(long = "no-watch-config")]
_no_watch_config: bool,
}
fn main() -> Result<()> {
// parse command line arguments into options struct
let opt = Opt::from_args();
setup_logger(opt.log_file.as_ref())?;
let rt = Runtime::new().unwrap();
rt.block_on(run(opt)).unwrap();
Ok(())
}
// #[tokio::main(flavor = "multi_thread")]
async fn run(opt: Opt) -> Result<()> {
let _xdg = BaseDirectories::new()?;
let (_config_thread, config_update) = spawn_config_watcher_system()?;
// used to notify the runtime that the process should exit
let (exit_tx, mut exit_rx) = mpsc::channel::<()>(1);
// send messages from the UI thread to the mail thread
let (ui2mail_tx, ui2mail_rx) = mpsc::unbounded_channel();
// send messages from the mail thread to the UI thread
let (mail2ui_tx, mail2ui_rx) = mpsc::unbounded_channel();
tokio::spawn(async move {
let config_update = config_update.clone();
mail::run_mail(config_update, ui2mail_rx, mail2ui_tx)
.unwrap_or_else(report_err)
.await;
});
if !opt.headless {
run_ui(exit_tx);
}
exit_rx.recv().await;
// TODO: graceful shutdown
// yada yada create a background process and pass off the connections so they can be safely
// shutdown
std::process::exit(0);
// Ok(())
}
// Spawns the entire UI in a different thread, since it must be thread-local
fn run_ui(exit_tx: mpsc::Sender<()>) {
let stdout = std::io::stdout();
let rt = RuntimeBuilder::new_current_thread()
.enable_all()
.build()
.unwrap();
thread::spawn(move || {
let localset = LocalSet::new();
localset.spawn_local(async {
ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err).await;
});
rt.block_on(localset);
});
}
fn setup_logger(log_file: Option<impl AsRef<Path>>) -> Result<()> {
let now = chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]");
let colors = ColoredLevelConfig::new()
.info(Color::Blue)
.debug(Color::BrightBlack)
.warn(Color::Yellow)
.error(Color::Red);
let mut logger = fern::Dispatch::new()
.format(move |out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
now,
record.target(),
colors.color(record.level()),
message
))
})
.level(log::LevelFilter::Debug);
if let Some(log_file) = log_file {
logger = logger.chain(fern::log_file(log_file)?);
}
logger.apply()?;
Ok(())
}