//! Daemon server — owns the agent state, listens on a per-session Unix //! socket, and drives one attached client at a time. //! //! Flow: `run_daemon()` creates a session + lock → binds a Unix socket //! under `/run/.sock` → blocks for a single client to //! `accept()` → loops reading `ClientRequest`s, translating each into //! `Action`(s) via the same `handle_key`/`apply_action` path the //! single-process mode uses, then pushes a full state update back → //! on `Close` or client disconnect, cleans up the socket file, saves //! settings, and releases the lock. //! //! Why: reuses `crate::handler::handle_key` by synthesising a //! `crossterm::KeyEvent` from the IPC `KeyAction`, so daemon and //! single-process modes share identical key-handling logic. use anyhow::Result; use zesdex_infrastructure::ipc::server::IpcServer; use crate::handler::handle_daemon_client; use crate::state::create_session; /// Run zesdex as a background daemon: owns the agent state, listens on a /// per-session Unix socket, and drives one attached client. /// /// Flow: create session + lock it → bind a Unix socket under /// `/run/.sock` → block for a single client to /// `accept()` → loop reading `ClientRequest`s, translating each into /// `Action`(s) → on `Close` or client disconnect, clean up the socket file, /// save settings, and release the lock. pub fn run_daemon() -> Result<()> { tracing::info!("starting daemon process"); let (store, _session_lock_guard, mut state, _rt) = create_session()?; let run_dir = store.base_dir.join("run"); std::fs::create_dir_all(&run_dir)?; let socket_path = run_dir.join(format!("{}.sock", state.session_id)); let addr = socket_path.to_string_lossy().to_string(); let server = IpcServer::bind_unix(&addr)?; tracing::info!("daemon listening on {addr}"); loop { let conn = match server.accept() { Ok(c) => c, Err(e) => { tracing::error!("daemon accept error: {e}"); break; } }; tracing::info!("daemon client connected"); if let Err(e) = handle_daemon_client(conn, &mut state) { tracing::error!("daemon error handling client: {e}"); } tracing::info!("daemon client disconnected"); state.save_settings(); } let _ = std::fs::remove_file(&socket_path); Ok(()) }