use std::collections::VecDeque;
use std::fmt;
use std::sync::{Arc, Mutex};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct IpcCall {
pub id: String,
pub command: String,
pub timestamp: DateTime<Utc>,
pub duration_ms: Option<u64>,
pub result: IpcResult,
pub arg_size_bytes: usize,
pub webview_label: String,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum IpcResult {
Pending,
Ok(serde_json::Value),
Err(String),
}
impl fmt::Display for IpcResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pending => f.write_str("pending"),
Self::Ok(_) => f.write_str("ok"),
Self::Err(msg) => write!(f, "error: {msg}"),
}
}
}
impl fmt::Display for IpcCall {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} [{}] \u{2192} {}", self.command, self.id, self.result)
}
}
impl From<IpcCall> for AppEvent {
fn from(call: IpcCall) -> Self {
Self::Ipc(call)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum InteractionKind {
Click,
DoubleClick,
Fill,
KeyPress,
Select,
Navigate,
Scroll,
}
impl fmt::Display for InteractionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Click => f.write_str("click"),
Self::DoubleClick => f.write_str("double_click"),
Self::Fill => f.write_str("fill"),
Self::KeyPress => f.write_str("key_press"),
Self::Select => f.write_str("select"),
Self::Navigate => f.write_str("navigate"),
Self::Scroll => f.write_str("scroll"),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
#[non_exhaustive]
pub enum AppEvent {
Ipc(IpcCall),
StateChange {
key: String,
timestamp: DateTime<Utc>,
caused_by: Option<String>,
},
DomMutation {
webview_label: String,
timestamp: DateTime<Utc>,
mutation_count: u32,
},
DomInteraction {
action: InteractionKind,
selector: String,
value: Option<String>,
timestamp: DateTime<Utc>,
webview_label: String,
},
WindowEvent {
label: String,
event: String,
timestamp: DateTime<Utc>,
},
}
impl AppEvent {
#[must_use]
pub fn timestamp(&self) -> DateTime<Utc> {
match self {
Self::Ipc(call) => call.timestamp,
Self::StateChange { timestamp, .. }
| Self::DomMutation { timestamp, .. }
| Self::DomInteraction { timestamp, .. }
| Self::WindowEvent { timestamp, .. } => *timestamp,
}
}
}
#[derive(Debug, Clone)]
pub struct EventLog {
events: Arc<Mutex<VecDeque<AppEvent>>>,
max_capacity: usize,
}
impl EventLog {
#[must_use]
pub fn new(max_capacity: usize) -> Self {
Self {
events: Arc::new(Mutex::new(VecDeque::with_capacity(max_capacity))),
max_capacity,
}
}
#[must_use]
pub fn capacity(&self) -> usize {
self.max_capacity
}
pub fn push(&self, event: AppEvent) {
let mut events = crate::acquire_lock(&self.events, "EventLog");
if events.len() >= self.max_capacity {
events.pop_front();
}
events.push_back(event);
}
#[must_use]
pub fn snapshot(&self) -> Vec<AppEvent> {
crate::acquire_lock(&self.events, "EventLog")
.iter()
.cloned()
.collect()
}
#[must_use]
pub fn snapshot_range(&self, offset: usize, limit: usize) -> Vec<AppEvent> {
crate::acquire_lock(&self.events, "EventLog")
.iter()
.skip(offset)
.take(limit)
.cloned()
.collect()
}
#[must_use]
pub fn since(&self, timestamp: DateTime<Utc>) -> Vec<AppEvent> {
crate::acquire_lock(&self.events, "EventLog")
.iter()
.filter(|e| e.timestamp() >= timestamp)
.cloned()
.collect()
}
#[must_use]
pub fn ipc_calls(&self) -> Vec<IpcCall> {
crate::acquire_lock(&self.events, "EventLog")
.iter()
.filter_map(|e| match e {
AppEvent::Ipc(call) => Some(call.clone()),
_ => None,
})
.collect()
}
#[must_use]
pub fn ipc_calls_since(&self, timestamp: DateTime<Utc>) -> Vec<IpcCall> {
crate::acquire_lock(&self.events, "EventLog")
.iter()
.filter_map(|e| match e {
AppEvent::Ipc(call) if call.timestamp >= timestamp => Some(call.clone()),
_ => None,
})
.collect()
}
#[must_use]
pub fn len(&self) -> usize {
crate::acquire_lock(&self.events, "EventLog").len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
crate::acquire_lock(&self.events, "EventLog").is_empty()
}
pub fn clear(&self) {
crate::acquire_lock(&self.events, "EventLog").clear();
}
}