//! IPC protocol definitions. //! //! Defines the message format and commands for IPC communication. use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::config::Settings; use crate::recording::RecordingResult; use crate::state::DaemonStatus; use crate::timeline::RecordingMetadata; /// IPC message wrapper. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IpcMessage { /// Message type. #[serde(rename = "type")] pub message_type: MessageType, /// Unique message ID. pub id: Uuid, /// Command name (for requests). #[serde(skip_serializing_if = "Option::is_none")] pub command: Option, /// Payload data. #[serde(skip_serializing_if = "Option::is_none")] pub payload: Option, } /// Message type. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum MessageType { /// Request message (expects response). Request, /// Response message. Response, /// Notification message (no response expected). Notification, } /// IPC commands that can be sent to the daemon. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub enum IpcCommand { // Configuration commands /// Get current settings. GetSettings, /// Update settings. UpdateSettings, /// Reset settings to defaults. ResetSettings, // Status commands /// Get daemon status. GetStatus, /// Get available encoders. GetEncoders, // Recording commands /// Start recording manually. StartRecording, /// Stop recording. StopRecording, /// Get list of recordings. GetRecordings, /// Get specific recording details. GetRecording { id: Uuid }, /// Delete a recording. DeleteRecording { id: Uuid }, // Timeline commands /// Get timeline for a recording. GetTimeline { recording_id: Uuid }, /// Export timeline to file. ExportTimeline { recording_id: Uuid, format: ExportFormat, }, // Daemon control /// Shutdown the daemon. Shutdown, } /// Export format for timeline. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum ExportFormat { /// JSON format. Json, /// CSV format. Csv, } /// IPC response. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IpcResponse { /// Original request ID. pub request_id: Uuid, /// Whether the request was successful. pub success: bool, /// Response data (if successful). #[serde(skip_serializing_if = "Option::is_none")] pub data: Option, /// Error message (if failed). #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, } impl IpcResponse { /// Create a successful response. pub fn success(request_id: Uuid, data: impl Serialize) -> Self { Self { request_id, success: true, data: Some(serde_json::to_value(data).unwrap_or(serde_json::Value::Null)), error: None, } } /// Create an error response. pub fn error(request_id: Uuid, error: impl Into) -> Self { Self { request_id, success: false, data: None, error: Some(error.into()), } } /// Serialize response to JSON string. pub fn to_json(&self) -> Result { serde_json::to_string(self) } } /// IPC notification (server-initiated message). #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "notification", rename_all = "camelCase")] pub enum IpcNotification { /// Recording started. RecordingStarted { recording_id: Uuid, game_id: Option, champion: Option, }, /// Recording stopped. RecordingStopped { recording_id: Uuid, result: RecordingResult, }, /// Game event received. GameEvent { event_type: String, raw_data: serde_json::Value, }, /// Daemon status changed. StatusChanged { status: DaemonStatus }, /// League Client connection changed. ClientConnectionChanged { connected: bool }, /// Settings updated. SettingsUpdated { settings: Settings }, /// Error occurred. Error { message: String }, } /// Response data types for specific commands. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GetSettingsResponse { pub settings: Settings, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GetStatusResponse { pub status: DaemonStatus, pub is_recording: bool, pub current_game_id: Option, pub client_connected: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GetRecordingsResponse { pub recordings: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GetEncodersResponse { pub available: Vec, pub current: String, } /// Information about an available encoder. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EncoderInfo { /// Encoder ID. pub id: String, /// Display name. pub name: String, /// Whether this is a hardware encoder. pub is_hardware: bool, /// Whether this encoder is available on this system. pub available: bool, } impl IpcMessage { /// Create a new request message. pub fn request(command: IpcCommand, payload: Option) -> Self { Self { message_type: MessageType::Request, id: Uuid::new_v4(), command: Some(command), payload, } } /// Create a new notification message. pub fn notification(notification: IpcNotification) -> Self { Self { message_type: MessageType::Notification, id: Uuid::new_v4(), command: None, payload: Some(serde_json::to_value(notification).unwrap_or(serde_json::Value::Null)), } } /// Parse from JSON string. pub fn from_json(json: &str) -> Result { serde_json::from_str(json) } /// Serialize to JSON string. pub fn to_json(&self) -> Result { serde_json::to_string(self) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_ipc_message_creation() { let msg = IpcMessage::request(IpcCommand::GetSettings, None); assert_eq!(msg.message_type, MessageType::Request); assert_eq!(msg.command, Some(IpcCommand::GetSettings)); } #[test] fn test_ipc_response_success() { let id = Uuid::new_v4(); let response = IpcResponse::success(id, "test data"); assert!(response.success); assert!(response.data.is_some()); assert!(response.error.is_none()); } #[test] fn test_ipc_response_error() { let id = Uuid::new_v4(); let response = IpcResponse::error(id, "Something went wrong"); assert!(!response.success); assert!(response.data.is_none()); assert!(response.error.is_some()); } #[test] fn test_message_serialization() { let msg = IpcMessage::request(IpcCommand::GetStatus, None); let json = msg.to_json().unwrap(); let parsed = IpcMessage::from_json(&json).unwrap(); assert_eq!(msg.command, parsed.command); } }