1use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use std::collections::VecDeque;
7use std::sync::{Arc, Mutex};
8
9use crate::error::VictauriError;
10use crate::event::{AppEvent, IpcCall};
11
12const DEFAULT_MAX_CHECKPOINTS: usize = 1000;
13const DEFAULT_MAX_EVENTS: usize = 50_000;
14
15#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
17pub struct StateCheckpoint {
18 pub id: String,
20 pub label: Option<String>,
22 pub timestamp: DateTime<Utc>,
24 pub state: serde_json::Value,
26 pub event_index: usize,
28}
29
30#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
32pub struct RecordedSession {
33 pub id: String,
35 pub started_at: DateTime<Utc>,
37 pub events: Vec<RecordedEvent>,
39 pub checkpoints: Vec<StateCheckpoint>,
41}
42
43#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
45pub struct RecordedEvent {
46 pub index: usize,
48 pub timestamp: DateTime<Utc>,
50 pub event: AppEvent,
52}
53
54#[derive(Debug, Clone)]
57pub struct EventRecorder {
58 recording: Arc<Mutex<Option<ActiveRecording>>>,
59 last_session: Arc<Mutex<Option<RecordedSession>>>,
60 max_events: usize,
61}
62
63#[derive(Debug, Clone)]
64struct ActiveRecording {
65 session_id: String,
66 started_at: DateTime<Utc>,
67 events: VecDeque<RecordedEvent>,
68 checkpoints: VecDeque<StateCheckpoint>,
69 event_counter: usize,
70 max_events: usize,
71 max_checkpoints: usize,
72}
73
74impl EventRecorder {
75 #[must_use]
85 pub fn new(max_events: usize) -> Self {
86 Self {
87 recording: Arc::new(Mutex::new(None)),
88 last_session: Arc::new(Mutex::new(None)),
89 max_events,
90 }
91 }
92
93 pub fn start(&self, session_id: String) -> crate::error::Result<()> {
109 let mut rec = crate::acquire_lock(&self.recording, "EventRecorder");
110 if rec.is_some() {
111 return Err(VictauriError::RecordingAlreadyActive);
112 }
113 *rec = Some(ActiveRecording {
114 session_id,
115 started_at: Utc::now(),
116 events: VecDeque::new(),
117 checkpoints: VecDeque::new(),
118 event_counter: 0,
119 max_events: self.max_events,
120 max_checkpoints: DEFAULT_MAX_CHECKPOINTS,
121 });
122 Ok(())
123 }
124
125 #[must_use]
139 pub fn stop(&self) -> Option<RecordedSession> {
140 let mut rec = crate::acquire_lock(&self.recording, "EventRecorder");
141 rec.take().map(|r| {
142 let session = RecordedSession {
143 id: r.session_id,
144 started_at: r.started_at,
145 events: r.events.into_iter().collect(),
146 checkpoints: r.checkpoints.into_iter().collect(),
147 };
148 *crate::acquire_lock(&self.last_session, "EventRecorder::last_session") =
149 Some(session.clone());
150 session
151 })
152 }
153
154 #[must_use]
156 pub fn is_recording(&self) -> bool {
157 crate::acquire_lock(&self.recording, "EventRecorder").is_some()
158 }
159
160 pub fn record_event(&self, event: AppEvent) {
162 let mut rec = crate::acquire_lock(&self.recording, "EventRecorder");
163 if let Some(ref mut active) = *rec {
164 let timestamp = event.timestamp();
165 let index = active.event_counter;
166 active.event_counter += 1;
167
168 if active.events.len() >= active.max_events {
169 active.events.pop_front();
170 }
171
172 active.events.push_back(RecordedEvent {
173 index,
174 timestamp,
175 event,
176 });
177 }
178 }
179
180 pub fn checkpoint(
186 &self,
187 id: String,
188 label: Option<String>,
189 state: serde_json::Value,
190 ) -> crate::error::Result<()> {
191 let mut rec = crate::acquire_lock(&self.recording, "EventRecorder");
192 if let Some(ref mut active) = *rec {
193 let event_index = active.event_counter;
194 if active.checkpoints.len() >= active.max_checkpoints {
195 active.checkpoints.pop_front();
196 }
197 active.checkpoints.push_back(StateCheckpoint {
198 id,
199 label,
200 timestamp: Utc::now(),
201 state,
202 event_index,
203 });
204 Ok(())
205 } else {
206 Err(VictauriError::NoActiveRecording)
207 }
208 }
209
210 #[must_use]
212 pub fn event_count(&self) -> usize {
213 crate::acquire_lock(&self.recording, "EventRecorder")
214 .as_ref()
215 .map_or(0, |r| r.events.len())
216 }
217
218 #[must_use]
220 pub fn checkpoint_count(&self) -> usize {
221 crate::acquire_lock(&self.recording, "EventRecorder")
222 .as_ref()
223 .map_or(0, |r| r.checkpoints.len())
224 }
225
226 #[must_use]
229 pub fn events_since(&self, index: usize) -> Vec<RecordedEvent> {
230 let rec = crate::acquire_lock(&self.recording, "EventRecorder");
231 if let Some(active) = rec.as_ref() {
232 return active
233 .events
234 .iter()
235 .filter(|e| e.index >= index)
236 .cloned()
237 .collect();
238 }
239 drop(rec);
240 let last = crate::acquire_lock(&self.last_session, "EventRecorder::last_session");
241 last.as_ref().map_or_else(Vec::new, |session| {
242 session
243 .events
244 .iter()
245 .filter(|e| e.index >= index)
246 .cloned()
247 .collect()
248 })
249 }
250
251 #[must_use]
254 pub fn events_between(&self, from: DateTime<Utc>, to: DateTime<Utc>) -> Vec<RecordedEvent> {
255 let rec = crate::acquire_lock(&self.recording, "EventRecorder");
256 if let Some(active) = rec.as_ref() {
257 return active
258 .events
259 .iter()
260 .filter(|e| e.timestamp >= from && e.timestamp <= to)
261 .cloned()
262 .collect();
263 }
264 drop(rec);
265 let last = crate::acquire_lock(&self.last_session, "EventRecorder::last_session");
266 last.as_ref().map_or_else(Vec::new, |session| {
267 session
268 .events
269 .iter()
270 .filter(|e| e.timestamp >= from && e.timestamp <= to)
271 .cloned()
272 .collect()
273 })
274 }
275
276 #[must_use]
279 pub fn get_checkpoints(&self) -> Vec<StateCheckpoint> {
280 let rec = crate::acquire_lock(&self.recording, "EventRecorder");
281 if let Some(active) = rec.as_ref() {
282 return active.checkpoints.iter().cloned().collect();
283 }
284 drop(rec);
285 let last = crate::acquire_lock(&self.last_session, "EventRecorder::last_session");
286 last.as_ref()
287 .map_or_else(Vec::new, |session| session.checkpoints.to_vec())
288 }
289
290 pub fn events_between_checkpoints(
298 &self,
299 from_checkpoint_id: &str,
300 to_checkpoint_id: &str,
301 ) -> crate::error::Result<Vec<RecordedEvent>> {
302 let rec = crate::acquire_lock(&self.recording, "EventRecorder");
303 let source_checkpoints;
304 let source_events;
305 if let Some(active) = rec.as_ref() {
306 source_checkpoints = active.checkpoints.iter().cloned().collect::<Vec<_>>();
307 source_events = active.events.iter().cloned().collect::<Vec<_>>();
308 } else {
309 drop(rec);
310 let last = crate::acquire_lock(&self.last_session, "EventRecorder::last_session");
311 let session = last.as_ref().ok_or(VictauriError::NoActiveRecording)?;
312 source_checkpoints = session.checkpoints.clone();
313 source_events = session.events.clone();
314 }
315
316 let from_idx = source_checkpoints
317 .iter()
318 .find(|c| c.id == from_checkpoint_id)
319 .ok_or_else(|| VictauriError::CheckpointNotFound {
320 id: from_checkpoint_id.to_string(),
321 })?
322 .event_index;
323 let to_idx = source_checkpoints
324 .iter()
325 .find(|c| c.id == to_checkpoint_id)
326 .ok_or_else(|| VictauriError::CheckpointNotFound {
327 id: to_checkpoint_id.to_string(),
328 })?
329 .event_index;
330
331 let (start, end) = if from_idx <= to_idx {
332 (from_idx, to_idx)
333 } else {
334 (to_idx, from_idx)
335 };
336
337 Ok(source_events
338 .iter()
339 .filter(|e| e.index >= start && e.index < end)
340 .cloned()
341 .collect())
342 }
343
344 #[must_use]
347 pub fn export(&self) -> Option<RecordedSession> {
348 let rec = crate::acquire_lock(&self.recording, "EventRecorder");
349 if let Some(r) = rec.as_ref() {
350 return Some(RecordedSession {
351 id: r.session_id.clone(),
352 started_at: r.started_at,
353 events: r.events.iter().cloned().collect(),
354 checkpoints: r.checkpoints.iter().cloned().collect(),
355 });
356 }
357 drop(rec);
358 crate::acquire_lock(&self.last_session, "EventRecorder::last_session").clone()
359 }
360
361 pub fn import(&self, session: RecordedSession) {
363 let event_counter = session.events.last().map_or(0, |e| e.index + 1);
364 let max_events = self.max_events;
365 let mut rec = crate::acquire_lock(&self.recording, "EventRecorder");
366 *rec = Some(ActiveRecording {
367 session_id: session.id,
368 started_at: session.started_at,
369 events: session.events.into_iter().collect(),
370 checkpoints: session.checkpoints.into_iter().collect(),
371 event_counter,
372 max_events,
373 max_checkpoints: DEFAULT_MAX_CHECKPOINTS,
374 });
375 }
376
377 #[must_use]
379 pub fn ipc_replay_sequence(&self) -> Vec<IpcCall> {
380 let rec = crate::acquire_lock(&self.recording, "EventRecorder");
381 if let Some(active) = rec.as_ref() {
382 return active
383 .events
384 .iter()
385 .filter_map(|re| match &re.event {
386 AppEvent::Ipc(call) => Some(call.clone()),
387 _ => None,
388 })
389 .collect();
390 }
391 drop(rec);
392 let last = crate::acquire_lock(&self.last_session, "EventRecorder::last_session");
393 last.as_ref().map_or_else(Vec::new, |session| {
394 session
395 .events
396 .iter()
397 .filter_map(|re| match &re.event {
398 AppEvent::Ipc(call) => Some(call.clone()),
399 _ => None,
400 })
401 .collect()
402 })
403 }
404}
405
406impl Default for EventRecorder {
407 fn default() -> Self {
408 Self::new(DEFAULT_MAX_EVENTS)
409 }
410}