use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use crate::error::VictauriError;
use crate::event::{AppEvent, IpcCall};
const DEFAULT_MAX_CHECKPOINTS: usize = 1000;
const DEFAULT_MAX_EVENTS: usize = 50_000;
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct StateCheckpoint {
pub id: String,
pub label: Option<String>,
pub timestamp: DateTime<Utc>,
pub state: serde_json::Value,
pub event_index: usize,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct RecordedSession {
pub id: String,
pub started_at: DateTime<Utc>,
pub events: Vec<RecordedEvent>,
pub checkpoints: Vec<StateCheckpoint>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct RecordedEvent {
pub index: usize,
pub timestamp: DateTime<Utc>,
pub event: AppEvent,
}
#[derive(Debug, Clone)]
pub struct EventRecorder {
recording: Arc<Mutex<Option<ActiveRecording>>>,
max_events: usize,
}
#[derive(Debug, Clone)]
struct ActiveRecording {
session_id: String,
started_at: DateTime<Utc>,
events: VecDeque<RecordedEvent>,
checkpoints: VecDeque<StateCheckpoint>,
event_counter: usize,
max_events: usize,
max_checkpoints: usize,
}
impl EventRecorder {
#[must_use]
pub fn new(max_events: usize) -> Self {
Self {
recording: Arc::new(Mutex::new(None)),
max_events,
}
}
pub fn start(&self, session_id: String) -> crate::error::Result<()> {
let mut rec = crate::acquire_lock(&self.recording, "EventRecorder");
if rec.is_some() {
return Err(VictauriError::RecordingAlreadyActive);
}
*rec = Some(ActiveRecording {
session_id,
started_at: Utc::now(),
events: VecDeque::new(),
checkpoints: VecDeque::new(),
event_counter: 0,
max_events: self.max_events,
max_checkpoints: DEFAULT_MAX_CHECKPOINTS,
});
Ok(())
}
#[must_use]
pub fn stop(&self) -> Option<RecordedSession> {
let mut rec = crate::acquire_lock(&self.recording, "EventRecorder");
rec.take().map(|r| RecordedSession {
id: r.session_id,
started_at: r.started_at,
events: r.events.into_iter().collect(),
checkpoints: r.checkpoints.into_iter().collect(),
})
}
#[must_use]
pub fn is_recording(&self) -> bool {
crate::acquire_lock(&self.recording, "EventRecorder").is_some()
}
pub fn record_event(&self, event: AppEvent) {
let mut rec = crate::acquire_lock(&self.recording, "EventRecorder");
if let Some(ref mut active) = *rec {
let timestamp = event.timestamp();
let index = active.event_counter;
active.event_counter += 1;
if active.events.len() >= active.max_events {
active.events.pop_front();
}
active.events.push_back(RecordedEvent {
index,
timestamp,
event,
});
}
}
pub fn checkpoint(
&self,
id: String,
label: Option<String>,
state: serde_json::Value,
) -> crate::error::Result<()> {
let mut rec = crate::acquire_lock(&self.recording, "EventRecorder");
if let Some(ref mut active) = *rec {
let event_index = active.event_counter;
if active.checkpoints.len() >= active.max_checkpoints {
active.checkpoints.pop_front();
}
active.checkpoints.push_back(StateCheckpoint {
id,
label,
timestamp: Utc::now(),
state,
event_index,
});
Ok(())
} else {
Err(VictauriError::NoActiveRecording)
}
}
#[must_use]
pub fn event_count(&self) -> usize {
crate::acquire_lock(&self.recording, "EventRecorder")
.as_ref()
.map_or(0, |r| r.events.len())
}
#[must_use]
pub fn checkpoint_count(&self) -> usize {
crate::acquire_lock(&self.recording, "EventRecorder")
.as_ref()
.map_or(0, |r| r.checkpoints.len())
}
#[must_use]
pub fn events_since(&self, index: usize) -> Vec<RecordedEvent> {
let rec = crate::acquire_lock(&self.recording, "EventRecorder");
match rec.as_ref() {
Some(active) => active
.events
.iter()
.filter(|e| e.index >= index)
.cloned()
.collect(),
None => Vec::new(),
}
}
#[must_use]
pub fn events_between(&self, from: DateTime<Utc>, to: DateTime<Utc>) -> Vec<RecordedEvent> {
let rec = crate::acquire_lock(&self.recording, "EventRecorder");
match rec.as_ref() {
Some(active) => active
.events
.iter()
.filter(|e| e.timestamp >= from && e.timestamp <= to)
.cloned()
.collect(),
None => Vec::new(),
}
}
#[must_use]
pub fn get_checkpoints(&self) -> Vec<StateCheckpoint> {
let rec = crate::acquire_lock(&self.recording, "EventRecorder");
match rec.as_ref() {
Some(active) => active.checkpoints.iter().cloned().collect(),
None => Vec::new(),
}
}
pub fn events_between_checkpoints(
&self,
from_checkpoint_id: &str,
to_checkpoint_id: &str,
) -> crate::error::Result<Vec<RecordedEvent>> {
let rec = crate::acquire_lock(&self.recording, "EventRecorder");
let active = rec.as_ref().ok_or(VictauriError::NoActiveRecording)?;
let from_idx = active
.checkpoints
.iter()
.find(|c| c.id == from_checkpoint_id)
.ok_or_else(|| VictauriError::CheckpointNotFound {
id: from_checkpoint_id.to_string(),
})?
.event_index;
let to_idx = active
.checkpoints
.iter()
.find(|c| c.id == to_checkpoint_id)
.ok_or_else(|| VictauriError::CheckpointNotFound {
id: to_checkpoint_id.to_string(),
})?
.event_index;
let (start, end) = if from_idx <= to_idx {
(from_idx, to_idx)
} else {
(to_idx, from_idx)
};
Ok(active
.events
.iter()
.filter(|e| e.index >= start && e.index < end)
.cloned()
.collect())
}
#[must_use]
pub fn export(&self) -> Option<RecordedSession> {
let rec = crate::acquire_lock(&self.recording, "EventRecorder");
rec.as_ref().map(|r| RecordedSession {
id: r.session_id.clone(),
started_at: r.started_at,
events: r.events.iter().cloned().collect(),
checkpoints: r.checkpoints.iter().cloned().collect(),
})
}
pub fn import(&self, session: RecordedSession) {
let event_counter = session.events.last().map_or(0, |e| e.index + 1);
let max_events = self.max_events;
let mut rec = crate::acquire_lock(&self.recording, "EventRecorder");
*rec = Some(ActiveRecording {
session_id: session.id,
started_at: session.started_at,
events: session.events.into_iter().collect(),
checkpoints: session.checkpoints.into_iter().collect(),
event_counter,
max_events,
max_checkpoints: DEFAULT_MAX_CHECKPOINTS,
});
}
#[must_use]
pub fn ipc_replay_sequence(&self) -> Vec<IpcCall> {
let rec = crate::acquire_lock(&self.recording, "EventRecorder");
match rec.as_ref() {
Some(active) => active
.events
.iter()
.filter_map(|re| match &re.event {
AppEvent::Ipc(call) => Some(call.clone()),
_ => None,
})
.collect(),
None => Vec::new(),
}
}
}
impl Default for EventRecorder {
fn default() -> Self {
Self::new(DEFAULT_MAX_EVENTS)
}
}