#![cfg_attr(docsrs, feature(doc_cfg))]
mod base;
#[doc = include_str!("../README.md")]
mod readme_tests {}
#[cfg(windows)]
mod win_service;
pub use base::BaseService;
use std::{
sync::mpsc::{Receiver, RecvTimeoutError, channel},
time::Duration,
};
#[cfg(windows)]
use win_service::start_service;
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
pub trait ServiceApp {
fn name(&self) -> &str;
fn start(&mut self) -> Result<()>;
fn stop(self: Box<Self>) -> Result<()>;
fn is_running(&self) -> bool;
}
#[cfg(not(windows))]
fn start_service(app: Box<dyn ServiceApp + Send>) -> Result<()> {
run_interactive(app)
}
fn run_interactive(mut app: Box<dyn ServiceApp + Send>) -> Result<()> {
app.start()?;
wait_for_shutdown(&*app)?;
app.stop()?;
Ok(())
}
pub fn run_service(app: impl ServiceApp + Send + 'static, service_mode: bool) -> Result<()> {
let app = Box::new(app);
if service_mode {
start_service(app)
} else {
run_interactive(app)
}
}
fn wait_for_shutdown(app: &dyn ServiceApp) -> Result<()> {
let (tx, rx) = channel();
ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel."))?;
wait_for_shutdown_or_exit(rx, app)?;
Ok(())
}
fn wait_for_shutdown_or_exit(shutdown_rx: Receiver<()>, app: &dyn ServiceApp) -> Result<()> {
while app.is_running() {
match shutdown_rx.recv_timeout(Duration::from_millis(100)) {
Ok(_) => break,
Err(RecvTimeoutError::Timeout) => continue,
Err(err) => return Err(err.into()),
}
}
Ok(())
}