record-daemon: initial commit

This commit is contained in:
2026-03-19 17:48:07 +01:00
commit d6c0334369
30 changed files with 9486 additions and 0 deletions

View 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);
}
}