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 = active.event_counter.saturating_add(1);
170
171 if active.events.len() >= active.max_events {
172 active.events.pop_front();
173 }
174
175 active.events.push_back(RecordedEvent {
176 index,
177 timestamp,
178 event,
179 });
180 }
181 }
182
183 pub fn checkpoint(
189 &self,
190 id: String,
191 label: Option<String>,
192 state: serde_json::Value,
193 ) -> crate::error::Result<()> {
194 let mut rec = crate::acquire_lock(&self.recording, "EventRecorder");
195 if let Some(ref mut active) = *rec {
196 let event_index = active.event_counter;
197 if active.checkpoints.len() >= active.max_checkpoints {
198 active.checkpoints.pop_front();
199 }
200 active.checkpoints.push_back(StateCheckpoint {
201 id,
202 label,
203 timestamp: Utc::now(),
204 state,
205 event_index,
206 });
207 Ok(())
208 } else {
209 Err(VictauriError::NoActiveRecording)
210 }
211 }
212
213 #[must_use]
215 pub fn event_count(&self) -> usize {
216 crate::acquire_lock(&self.recording, "EventRecorder")
217 .as_ref()
218 .map_or(0, |r| r.events.len())
219 }
220
221 #[must_use]
223 pub fn checkpoint_count(&self) -> usize {
224 crate::acquire_lock(&self.recording, "EventRecorder")
225 .as_ref()
226 .map_or(0, |r| r.checkpoints.len())
227 }
228
229 #[must_use]
232 pub fn events_since(&self, index: usize) -> Vec<RecordedEvent> {
233 let rec = crate::acquire_lock(&self.recording, "EventRecorder");
234 if let Some(active) = rec.as_ref() {
235 return active
236 .events
237 .iter()
238 .filter(|e| e.index >= index)
239 .cloned()
240 .collect();
241 }
242 drop(rec);
243 let last = crate::acquire_lock(&self.last_session, "EventRecorder::last_session");
244 last.as_ref().map_or_else(Vec::new, |session| {
245 session
246 .events
247 .iter()
248 .filter(|e| e.index >= index)
249 .cloned()
250 .collect()
251 })
252 }
253
254 #[must_use]
257 pub fn events_between(&self, from: DateTime<Utc>, to: DateTime<Utc>) -> Vec<RecordedEvent> {
258 let rec = crate::acquire_lock(&self.recording, "EventRecorder");
259 if let Some(active) = rec.as_ref() {
260 return active
261 .events
262 .iter()
263 .filter(|e| e.timestamp >= from && e.timestamp <= to)
264 .cloned()
265 .collect();
266 }
267 drop(rec);
268 let last = crate::acquire_lock(&self.last_session, "EventRecorder::last_session");
269 last.as_ref().map_or_else(Vec::new, |session| {
270 session
271 .events
272 .iter()
273 .filter(|e| e.timestamp >= from && e.timestamp <= to)
274 .cloned()
275 .collect()
276 })
277 }
278
279 #[must_use]
282 pub fn get_checkpoints(&self) -> Vec<StateCheckpoint> {
283 let rec = crate::acquire_lock(&self.recording, "EventRecorder");
284 if let Some(active) = rec.as_ref() {
285 return active.checkpoints.iter().cloned().collect();
286 }
287 drop(rec);
288 let last = crate::acquire_lock(&self.last_session, "EventRecorder::last_session");
289 last.as_ref()
290 .map_or_else(Vec::new, |session| session.checkpoints.to_vec())
291 }
292
293 pub fn events_between_checkpoints(
301 &self,
302 from_checkpoint_id: &str,
303 to_checkpoint_id: &str,
304 ) -> crate::error::Result<Vec<RecordedEvent>> {
305 let rec = crate::acquire_lock(&self.recording, "EventRecorder");
306 let source_checkpoints;
307 let source_events;
308 if let Some(active) = rec.as_ref() {
309 source_checkpoints = active.checkpoints.iter().cloned().collect::<Vec<_>>();
310 source_events = active.events.iter().cloned().collect::<Vec<_>>();
311 } else {
312 drop(rec);
313 let last = crate::acquire_lock(&self.last_session, "EventRecorder::last_session");
314 let session = last.as_ref().ok_or(VictauriError::NoActiveRecording)?;
315 source_checkpoints = session.checkpoints.clone();
316 source_events = session.events.clone();
317 }
318
319 let from_idx = source_checkpoints
320 .iter()
321 .find(|c| c.id == from_checkpoint_id)
322 .ok_or_else(|| VictauriError::CheckpointNotFound {
323 id: from_checkpoint_id.to_string(),
324 })?
325 .event_index;
326 let to_idx = source_checkpoints
327 .iter()
328 .find(|c| c.id == to_checkpoint_id)
329 .ok_or_else(|| VictauriError::CheckpointNotFound {
330 id: to_checkpoint_id.to_string(),
331 })?
332 .event_index;
333
334 let (start, end) = if from_idx <= to_idx {
335 (from_idx, to_idx)
336 } else {
337 (to_idx, from_idx)
338 };
339
340 Ok(source_events
341 .iter()
342 .filter(|e| e.index >= start && e.index < end)
343 .cloned()
344 .collect())
345 }
346
347 #[must_use]
350 pub fn export(&self) -> Option<RecordedSession> {
351 let rec = crate::acquire_lock(&self.recording, "EventRecorder");
352 if let Some(r) = rec.as_ref() {
353 return Some(RecordedSession {
354 id: r.session_id.clone(),
355 started_at: r.started_at,
356 events: r.events.iter().cloned().collect(),
357 checkpoints: r.checkpoints.iter().cloned().collect(),
358 });
359 }
360 drop(rec);
361 crate::acquire_lock(&self.last_session, "EventRecorder::last_session").clone()
362 }
363
364 pub fn import(&self, session: RecordedSession) {
372 let max_events = self.max_events;
373 let max_checkpoints = DEFAULT_MAX_CHECKPOINTS;
374
375 let mut events: std::collections::VecDeque<RecordedEvent> =
376 session.events.into_iter().collect();
377 while events.len() > max_events {
378 events.pop_front();
379 }
380 let mut checkpoints: std::collections::VecDeque<StateCheckpoint> =
381 session.checkpoints.into_iter().collect();
382 while checkpoints.len() > max_checkpoints {
383 checkpoints.pop_front();
384 }
385
386 let event_counter = events.back().map_or(0, |e| e.index.saturating_add(1));
387
388 let mut rec = crate::acquire_lock(&self.recording, "EventRecorder");
389 *rec = Some(ActiveRecording {
390 session_id: session.id,
391 started_at: session.started_at,
392 events,
393 checkpoints,
394 event_counter,
395 max_events,
396 max_checkpoints,
397 });
398 }
399
400 #[must_use]
402 pub fn ipc_replay_sequence(&self) -> Vec<IpcCall> {
403 let rec = crate::acquire_lock(&self.recording, "EventRecorder");
404 if let Some(active) = rec.as_ref() {
405 return active
406 .events
407 .iter()
408 .filter_map(|re| match &re.event {
409 AppEvent::Ipc(call) => Some(call.clone()),
410 _ => None,
411 })
412 .collect();
413 }
414 drop(rec);
415 let last = crate::acquire_lock(&self.last_session, "EventRecorder::last_session");
416 last.as_ref().map_or_else(Vec::new, |session| {
417 session
418 .events
419 .iter()
420 .filter_map(|re| match &re.event {
421 AppEvent::Ipc(call) => Some(call.clone()),
422 _ => None,
423 })
424 .collect()
425 })
426 }
427}
428
429impl Default for EventRecorder {
430 fn default() -> Self {
431 Self::new(DEFAULT_MAX_EVENTS)
432 }
433}