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::{close_pool, 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    close_pool(&manager.pool);
119
120    Ok(())
121}
122
123#[doc(hidden)]
124fn setup() -> anyhow::Result<(Settings, Option<WorkerGuard>)> {
125    let args = Args::parse();
126
127    let settings = Settings::get_settings(args.config).context("failed to load settings");
128
129    match settings {
130        Ok(settings) => {
131            let guard = setup_logs(
132                &settings.app.log_level,
133                &settings.opts.log_file,
134                &(&settings.opts.log_file_rollover).into(),
135                settings.app.api_logging,
136            )?;
137
138            Ok((settings, guard))
139        }
140        Err(e) => {
141            setup_logs(
142                &autopulse_utils::LogLevel::Info,
143                &None,
144                &Rotation::NEVER,
145                false,
146            )?;
147
148            Err(e)
149        }
150    }
151}
152
153#[doc(hidden)]
154pub fn main() -> anyhow::Result<()> {
155    match setup() {
156        Ok((settings, guard)) => {
157            info!("💫 autopulse v{} starting up...", env!("CARGO_PKG_VERSION"),);
158
159            if let Err(e) = run(settings, guard) {
160                error!("{:?}", e);
161            }
162        }
163        Err(e) => {
164            error!("{:?}", e);
165        }
166    }
167
168    Ok(())
169}