Some checks are pending
record-daemon / Build, check and test (push) Waiting to run
280 lines
7.3 KiB
Rust
280 lines
7.3 KiB
Rust
//! 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<IpcCommand>,
|
|
/// Payload data.
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub payload: Option<serde_json::Value>,
|
|
}
|
|
|
|
/// 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<serde_json::Value>,
|
|
/// Error message (if failed).
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
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<String>) -> Self {
|
|
Self {
|
|
request_id,
|
|
success: false,
|
|
data: None,
|
|
error: Some(error.into()),
|
|
}
|
|
}
|
|
|
|
/// Serialize response to JSON string.
|
|
pub fn to_json(&self) -> Result<String, serde_json::Error> {
|
|
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<u64>,
|
|
champion: Option<String>,
|
|
},
|
|
|
|
/// 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<u64>,
|
|
pub client_connected: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct GetRecordingsResponse {
|
|
pub recordings: Vec<RecordingMetadata>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct GetEncodersResponse {
|
|
pub available: Vec<EncoderInfo>,
|
|
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<serde_json::Value>) -> 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<Self, serde_json::Error> {
|
|
serde_json::from_str(json)
|
|
}
|
|
|
|
/// Serialize to JSON string.
|
|
pub fn to_json(&self) -> Result<String, serde_json::Error> {
|
|
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);
|
|
}
|
|
}
|