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