1use std::{path::PathBuf, process::exit};
2
3use anyhow::Result;
4use async_trait::async_trait;
5use clap::{Arg, Command};
6use tokio::runtime;
7use tracing::instrument;
8
9use crate::logging;
10
11const VERSION: &str = env!("CARGO_PKG_VERSION");
12const MINIMUM_WORKER_THREAD_COUNT: usize = 6;
13
14#[async_trait]
15pub trait Daemon {
16    type Config;
17
18    fn load_config(&self, path_maybe: &Option<PathBuf>) -> Result<Self::Config>;
19
20    async fn start(&mut self, cfg: Self::Config) -> Result<()>;
21    async fn stop(&mut self) -> Result<()>;
22}
23
24fn worker_thread_count() -> usize {
25    let core_count = num_cpus::get();
26    core_count.max(MINIMUM_WORKER_THREAD_COUNT)
27}
28
29#[instrument(level = "trace")]
30fn init_runtime() -> Result<runtime::Runtime> {
31    let threads = worker_thread_count();
32    let rt = runtime::Builder::new_multi_thread()
33        .enable_io()
34        .enable_time()
35        .worker_threads(threads)
36        .build()?;
37    tracing::trace!(threads = threads, "initialized tokio runtime");
38    Ok(rt)
39}
40
41fn main_loop<D: Daemon>(
42    cfg_path: Option<PathBuf>,
43    log_config: Option<PathBuf>,
44    mut daemon: D,
45) -> Result<()> {
46    logging::init_logger(&log_config)?;
47    let cfg = daemon.load_config(&cfg_path)?;
48
49    let rt = init_runtime()?;
50    rt.block_on(async move {
51        daemon.start(cfg).await?;
52        tokio::signal::ctrl_c().await?;
53        daemon.stop().await?;
54
55        Ok(())
56    })
57}
58
59pub struct DaemonProcess {}
60
61impl DaemonProcess {
62    pub fn start<D: Daemon>(name: &str, about: &str, daemon: D) {
63        let matches = Command::new(name)
64            .version(VERSION)
65            .about(about)
66            .arg(
67                Arg::new("config")
68                    .short('c')
69                    .long("cfg")
70                    .value_name("FILE")
71                    .help("Sets a config file")
72                    .takes_value(true),
73            )
74            .arg(
75                Arg::new("log_config")
76                    .short('l')
77                    .long("log")
78                    .help("Sets the log config file")
79                    .takes_value(true),
80            )
81            .get_matches();
82
83        let cfg: Option<PathBuf> = matches.value_of_t("config").ok();
84
85        let log_config: Option<PathBuf> = matches.value_of_t("log_config").ok();
86
87        if let Err(e) = main_loop(cfg, log_config, daemon) {
88            tracing::error!("fatal: {}", e);
89            exit(1);
90        }
91    }
92}