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 tokio::signal::unix::{signal, SignalKind};
24use tracing::{debug, error, info};
25
26/// Arguments for CLI
27///
28/// ```
29/// $ autopulse --help
30/// ```
31#[derive(Parser, Debug)]
32#[command(version, about, long_about = None)]
33pub struct Args {
34    /// Location of configuration file
35    #[arg(short, long)]
36    pub config: Option<String>,
37}
38
39#[doc(hidden)]
40#[tokio::main]
41async fn run(settings: Settings, _guard: Option<WorkerGuard>) -> anyhow::Result<()> {
42    let hostname = settings.app.hostname.clone();
43    let port = settings.app.port;
44    let database_url = settings.app.database_url.clone();
45
46    AnyConnection::pre_init(&database_url)?;
47
48    let pool = get_pool(&database_url)?;
49    let mut conn = get_conn(&pool)?;
50
51    conn.migrate()?;
52
53    // drop conn to prevent deadlocks
54    drop(conn);
55
56    let manager = PulseManager::new(settings, pool.clone());
57    let manager = Arc::new(manager);
58
59    manager.start().await;
60    manager.start_webhooks().await;
61    manager.start_notify().await;
62
63    let manager_clone = manager.clone();
64
65    let server = get_server(hostname.clone(), port, manager_clone)?;
66
67    info!("🚀 listening on {}:{}", hostname, port);
68
69    let server_task = tokio::spawn(server);
70
71    let shutdown: tokio::task::JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
72        let mut sigterm = signal(SignalKind::terminate())?;
73        let mut sigint = signal(SignalKind::interrupt())?;
74
75        tokio::select! {
76            _ = sigterm.recv() => {
77                debug!("Received SIGTERM");
78            }
79            _ = sigint.recv() => {
80                debug!("Received SIGINT");
81            }
82        }
83
84        info!("💤 shutting down...");
85
86        Ok(())
87    });
88
89    shutdown.await??;
90
91    manager.shutdown().await?;
92    server_task.abort();
93
94    Ok(())
95}
96
97#[doc(hidden)]
98fn setup() -> anyhow::Result<(Settings, Option<WorkerGuard>)> {
99    let args = Args::parse();
100
101    let settings = Settings::get_settings(args.config).context("failed to load settings");
102
103    match settings {
104        Ok(settings) => {
105            let guard = setup_logs(
106                &settings.app.log_level,
107                &settings.opts.log_file,
108                settings.opts.log_file_rollover.clone().into(),
109                settings.app.api_logging,
110            )?;
111
112            Ok((settings, guard))
113        }
114        Err(e) => {
115            // still setup logs if settings failed to load
116            setup_logs(
117                &autopulse_utils::LogLevel::Info,
118                &None,
119                Rotation::NEVER,
120                false,
121            )?;
122
123            Err(e)
124        }
125    }
126}
127
128#[doc(hidden)]
129pub fn main() -> anyhow::Result<()> {
130    match setup() {
131        Ok((settings, guard)) => {
132            info!("💫 autopulse v{} starting up...", env!("CARGO_PKG_VERSION"),);
133
134            if let Err(e) = run(settings, guard) {
135                error!("{:?}", e);
136            }
137        }
138        Err(e) => {
139            error!("{:?}", e);
140        }
141    }
142
143    Ok(())
144}