core/
main.rs

1//! This crate contains the core components and functionalities of the feonix software.
2//! It is designed around the actor framework (see <https://ryhl.io/blog/actors-with-tokio/>),
3//! with all main functionalities being implemented as actors.
4//!
5//! The file `main.rs` contains the entry point for the application.
6
7mod camera;
8mod database;
9mod mailbox;
10mod sauron;
11mod utils;
12
13use postgresql_embedded::PostgreSQL;
14use std::env;
15use tracing::info;
16
17use dotenv::dotenv;
18use testcontainers::{ContainerAsync, GenericImage};
19
20use crate::{
21    database::{start_postgres_container, start_postgres_embedded},
22    mailbox::{start_logfile_broadcaster, start_telemetry_server},
23    utils::get_env,
24};
25use tokio_util::sync::CancellationToken;
26
27/// An enum representing the active PostgreSQL instance.
28/// Currently supports either an embedded server or a Docker container.
29#[derive(Debug)]
30enum PostgresTypes {
31    Embedded(PostgreSQL),
32    Container(ContainerAsync<GenericImage>),
33}
34
35#[tokio::main]
36async fn main() -> anyhow::Result<()> {
37    // Filter for different debug messages
38    // - Change "debug" to "trace" for more details or "info" for less details
39    let tracing_filter = tracing_subscriber::EnvFilter::default()
40        .add_directive("debug".parse()?)
41        .add_directive("ort::environment=off".parse()?); // too much spam from `ort::environment`
42    tracing_subscriber::fmt()
43        .with_env_filter(tracing_filter)
44        .init();
45
46    dotenv().ok();
47    utils::print_envs();
48
49    let db_type: String = get_env("DEV_DB_TYPE", "EMBEDDED".to_string());
50
51    let postgresql: PostgresTypes = match db_type.as_str() {
52        "CONTAINER" => {
53            info!("Starting container database");
54            PostgresTypes::Container(start_postgres_container().await?)
55        }
56        _ => {
57            info!("Starting an embedded database");
58            let (postgres, url) = start_postgres_embedded().await?;
59            unsafe {
60                env::set_var("DATABASE_URL", &url); // No threading before this statement is allowed
61            }
62            PostgresTypes::Embedded(postgres)
63        }
64    };
65
66    // Create a shared shutdown token for graceful shutdown coordination
67    let shutdown_token = CancellationToken::new();
68    let shutdown_token_clone = shutdown_token.clone();
69
70    // Set up ctrl-c handler that cancels the shutdown token
71    ctrlc::set_handler(move || {
72        info!("Received ctrl-c, initiating graceful shutdown");
73        shutdown_token_clone.cancel();
74    })?;
75
76    // --- Creating Actors ---
77    // Create sauron's actor
78    let sauron = sauron::SauronHandle::new().await;
79    // Create mailbox actor
80    let mailbox = mailbox::MailboxHandle::new().await;
81
82    // --- Orchestrating Actors ---
83    // Pass sauron actor into camera
84    camera::start_camera(sauron.clone()).await;
85
86    // Connect mailbox to sauron
87    sauron.set_mailbox(mailbox.clone()).await;
88
89    // Start logfile broadcaster with shared shutdown token
90    start_logfile_broadcaster(mailbox.clone(), shutdown_token.clone()).await;
91
92    start_telemetry_server(mailbox.clone(), shutdown_token.clone()).await;
93
94    // Automatically exit if in BUILD_ONLY mode
95    if get_env("BUILD_ONLY", false) {
96        shutdown_token.cancel();
97    }
98
99    // Wait for shutdown signal
100    shutdown_token.cancelled().await;
101    info!("Shutdown signal received, cleaning up...");
102
103    // Clean up database
104    info!("Stopping database");
105    match postgresql {
106        PostgresTypes::Container(container) => container.stop().await?,
107        PostgresTypes::Embedded(post) => post.stop().await?,
108    }
109
110    Ok(())
111}