//! Windows-Dienst-Integration (Service Control Manager). Nur unter Windows //! kompiliert (`#[cfg(windows)]` am `mod service` in `main.rs`). //! //! Ablauf: `run()` übergibt den Prozess an den SCM. Der SCM ruft `service_main` //! in einem eigenen Thread; dort registrieren wir einen Control-Handler //! (für Stop/Shutdown), melden „Running", starten eine eigene tokio-Runtime und //! lassen `crate::run_app` laufen, bis der Handler ein Stop-Signal schickt. use std::ffi::OsString; use std::sync::mpsc; use std::time::Duration; use windows_service::service::{ ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus, ServiceType, }; use windows_service::service_control_handler::{self, ServiceControlHandlerResult}; use windows_service::{define_windows_service, service_dispatcher}; /// Interner Dienst-Name (Schlüssel). **Muss** mit dem `-Name` im /// Install-Skript (`install-service.ps1`) übereinstimmen. Der Anzeigename /// „Holzleitner App Mails" wird separat beim Registrieren gesetzt. const SERVICE_NAME: &str = "HolzleitnerAppMails"; const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS; define_windows_service!(ffi_service_main, service_main); /// Übergibt den Prozess an den SCM. Gibt `Err` zurück, wenn der Prozess NICHT /// vom SCM gestartet wurde (interaktiver Start) — der Aufrufer fällt dann in /// den Konsolenmodus zurück. pub fn run() -> windows_service::Result<()> { service_dispatcher::start(SERVICE_NAME, ffi_service_main) } fn service_main(_args: Vec) { // Fehler hier landen (falls vor dem Logger) im fallback_log von run_app bzw. // werden vom SCM als nicht-laufend erkannt. let _ = run_service(); } fn run_service() -> windows_service::Result<()> { // Kanal: Control-Handler -> Loop. Stop/Shutdown sendet ein () hinein. let (shutdown_tx, shutdown_rx) = mpsc::channel::<()>(); let handler = move |control| -> ServiceControlHandlerResult { match control { ServiceControl::Stop | ServiceControl::Shutdown => { let _ = shutdown_tx.send(()); ServiceControlHandlerResult::NoError } ServiceControl::Interrogate => ServiceControlHandlerResult::NoError, _ => ServiceControlHandlerResult::NotImplemented, } }; let status_handle = service_control_handler::register(SERVICE_NAME, handler)?; // „Running" melden (akzeptiert Stop + System-Shutdown). status_handle.set_service_status(ServiceStatus { service_type: SERVICE_TYPE, current_state: ServiceState::Running, controls_accepted: ServiceControlAccept::STOP | ServiceControlAccept::SHUTDOWN, exit_code: ServiceExitCode::Win32(0), checkpoint: 0, wait_hint: Duration::default(), process_id: None, })?; // Eigene tokio-Runtime im Dienst-Thread (kein #[tokio::main]). let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .expect("tokio runtime"); rt.block_on(async move { // mpsc::recv ist blockierend → in spawn_blocking auslagern und als // Future an run_app übergeben. let shutdown = async move { let _ = tokio::task::spawn_blocking(move || { let _ = shutdown_rx.recv(); }) .await; }; crate::run_app(shutdown).await; }); // „Stopped" melden. status_handle.set_service_status(ServiceStatus { service_type: SERVICE_TYPE, current_state: ServiceState::Stopped, controls_accepted: ServiceControlAccept::empty(), exit_code: ServiceExitCode::Win32(0), checkpoint: 0, wait_hint: Duration::default(), process_id: None, })?; Ok(()) }