record-daemon: initial commit
This commit is contained in:
277
record-daemon/src/ipc/protocol.rs
Normal file
277
record-daemon/src/ipc/protocol.rs
Normal file
@@ -0,0 +1,277 @@
|
||||
//! 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::lqp::GameEvent;
|
||||
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: GameEvent },
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user