aboutsummaryrefslogtreecommitdiffstats
path: root/crates/daemon/src/lib.rs
blob: 3be39a1c4db94ff082b2ba50bd4abd5def20ee99 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#![expect(unused_crate_dependencies, reason = "Didn't remove them yet")]

use std::sync::Arc;

use crate::aclient::record::sqlite_store::SqliteStore;
use crate::aclient::settings::Settings;
use crate::api::server::control::ControlService;
use crate::{
    aclient::database::ClientSqlite as HistoryDatabase,
    api::generated::history::history_server::HistoryServer,
};
use eyre::Result;

pub mod aclient;

pub(crate) mod api;
pub(crate) mod components;
pub(crate) mod daemon;
pub(crate) mod events;
pub(crate) mod server;

// Re-export core daemon types for convenience
pub(crate) use daemon::Daemon;
pub use events::DaemonEvent;

// Re-export components
pub(crate) use components::{HistoryComponent, SyncComponent};

/// Boot the daemon using the new component-based architecture.
///
/// This creates a daemon with the standard components (history, search, sync),
/// starts the gRPC server with their services, and runs the event loop.
pub async fn boot(
    settings: Settings,
    store: SqliteStore,
    history_db: HistoryDatabase,
) -> Result<()> {
    // Create the components
    let history_component = Arc::new(HistoryComponent::new());
    let sync_component = Arc::new(Box::new(&SyncComponent::new()));

    // Get the gRPC services before moving components into the daemon
    // (The services share state with the components via Arc)
    let history_service = HistoryServer::from_arc(Arc::clone(&history_component));

    // Build the daemon
    let mut daemon = Daemon::builder(settings.clone())
        .store(store)
        .history_db(history_db)
        .component(history_component)
        .component(sync_component)
        .build()?;

    let handle = daemon.handle();

    let control_service = ControlService::new(handle.clone());

    // Start all components first (so gRPC services can work)
    daemon.start_components().await?;

    // Spawn signal handler to emit ShutdownRequested on Ctrl+C/SIGTERM
    let signal_handle = handle.clone();
    tokio::spawn(async move {
        shutdown_signal().await;
        tracing::info!("received shutdown signal");
        signal_handle.shutdown();
    });

    server::run_grpc_server(
        &settings,
        history_service,
        control_service.into_server(),
        handle,
    )?;

    daemon.run_event_loop().await?;

    // After the event loop exited, we shut-down the components.
    daemon.stop_components().await;

    tracing::info!("daemon shut down complete");
    Ok(())
}

/// Wait for a shutdown signal (Ctrl+C or SIGTERM).
#[cfg(unix)]
async fn shutdown_signal() {
    let mut term = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
        .expect("failed to register sigterm handler");
    let mut int = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())
        .expect("failed to register sigint handler");

    tokio::select! {
        _ = term.recv() => {},
        _ = int.recv() => {},
    }
}