autopulse/
main.rs

1//! automated scanning tool that integrates widely-used media management services with various media servers for seamless media organization
2//!
3//! ## Quick docs
4//!
5//! - **[Settings](autopulse_service::settings)**: Settings handler
6//!   - **[Triggers](autopulse_service::settings::triggers)**: Create triggers that will be executed by a service when a certain event occurs
7//!   - **[Targets](autopulse_service::settings::targets)**: Create targets that will be scanned by a service
8//!   - **[Webhooks](autopulse_service::settings::webhooks)**: Send webhooks to services to notify them of an event
9//! - **[Database](autopulse_database::conn::AnyConnection)**: Database handler
10//!
11//! ## About
12#![doc = include_str!("../README.md")]
13
14use anyhow::Context;
15use autopulse_database::conn::{get_conn, get_pool, AnyConnection};
16use autopulse_server::get_server;
17use autopulse_service::manager::PulseManager;
18use autopulse_service::settings::Settings;
19use autopulse_utils::tracing_appender::non_blocking::WorkerGuard;
20use autopulse_utils::{setup_logs, Rotation};
21use clap::Parser;
22use std::sync::Arc;
23use tracing::{debug, error, info};
24
25/// Arguments for CLI
26///
27/// ```
28/// $ autopulse --help
29/// ```
30#[derive(Parser, Debug)]
31#[command(version, about, long_about = None)]
32pub struct Args {
33    /// Location of configuration file
34    #[arg(short, long)]
35    pub config: Option<String>,
36}
37
38#[doc(hidden)]
39#[tokio::main]
40async fn run(settings: Settings, _guard: Option<WorkerGuard>) -> anyhow::Result<()> {
41    let hostname = settings.app.hostname.clone();
42    let port = settings.app.port;
43    let database_url = settings.app.database_url.clone();
44
45    AnyConnection::pre_init(&database_url)?;
46
47    let pool = get_pool(&database_url)?;
48    let mut conn = get_conn(&pool)?;
49
50    conn.migrate()?;
51
52    // drop conn to prevent deadlocks
53    drop(conn);
54
55    let manager = PulseManager::new(settings, pool.clone());
56    let manager = Arc::new(manager);
57
58    manager.start().await;
59    manager.start_webhooks().await;
60    manager.start_notify().await;
61
62    let manager_clone = manager.clone();
63
64    let server = get_server(hostname.clone(), port, manager_clone)?;
65
66    info!("🚀 listening on {}:{}", hostname, port);
67
68    let server_task = tokio::spawn(server);
69
70    let shutdown: tokio::task::JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
71        #[cfg(unix)]
72        {
73            use tokio::signal::unix::{signal, SignalKind};
74
75            let mut sigterm = signal(SignalKind::terminate())?;
76            let mut sigint = signal(SignalKind::interrupt())?;
77
78            tokio::select! {
79                _ = sigterm.recv() => {
80                    debug!("Received SIGTERM");
81                }
82                _ = sigint.recv() => {
83                    debug!("Received SIGINT");
84                }
85            }
86        }
87
88        #[cfg(windows)]
89        {
90            use tokio::signal::ctrl_c;
91
92            let ctrl_c = ctrl_c();
93
94            tokio::select! {
95                _ = ctrl_c => {
96                    debug!("Received Ctrl+C");
97                }
98            }
99        }
100
101        info!("💤 shutting down...");
102
103        Ok(())
104    });
105
106    shutdown.await??;
107
108    manager.shutdown().await?;
109    server_task.abort();
110
111    Ok(())
112}
113
114#[doc(hidden)]
115fn setup() -> anyhow::Result<(Settings, Option<WorkerGuard>)> {
116    let args = Args::parse();
117
118    let settings = Settings::get_settings(args.config).context("failed to load settings");
119
120    match settings {
121        Ok(settings) => {
122            let guard = setup_logs(
123                &settings.app.log_level,
124                &settings.opts.log_file,
125                settings.opts.log_file_rollover.clone().into(),
126                settings.app.api_logging,
127            )?;
128
129            Ok((settings, guard))
130        }
131        Err(e) => {
132            // still setup logs if settings failed to load
133            setup_logs(
134                &autopulse_utils::LogLevel::Info,
135                &None,
136                Rotation::NEVER,
137                false,
138            )?;
139
140            Err(e)
141        }
142    }
143}
144
145#[doc(hidden)]
146pub fn main() -> anyhow::Result<()> {
147    match setup() {
148        Ok((settings, guard)) => {
149            info!("💫 autopulse v{} starting up...", env!("CARGO_PKG_VERSION"),);
150
151            if let Err(e) = run(settings, guard) {
152                error!("{:?}", e);
153            }
154        }
155        Err(e) => {
156            error!("{:?}", e);
157        }
158    }
159
160    Ok(())
161}