synheart_sensor_agent/transparency/
log.rs1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::path::PathBuf;
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::sync::Arc;
11
12#[derive(Debug)]
14pub struct TransparencyLog {
15 keyboard_events: AtomicU64,
17 mouse_events: AtomicU64,
19 windows_completed: AtomicU64,
21 shortcut_events: AtomicU64,
23 snapshots_exported: AtomicU64,
25 session_start: DateTime<Utc>,
27 persist_path: Option<PathBuf>,
29}
30
31impl TransparencyLog {
32 pub fn new() -> Self {
34 Self {
35 keyboard_events: AtomicU64::new(0),
36 mouse_events: AtomicU64::new(0),
37 shortcut_events: AtomicU64::new(0),
38 windows_completed: AtomicU64::new(0),
39 snapshots_exported: AtomicU64::new(0),
40 session_start: Utc::now(),
41 persist_path: None,
42 }
43 }
44
45 pub fn with_persistence(path: PathBuf) -> Self {
47 let mut log = Self::new();
48 log.persist_path = Some(path);
49
50 if let Err(e) = log.load() {
52 eprintln!("Note: Could not load previous transparency stats: {e}");
53 }
54
55 log
56 }
57
58 pub fn record_keyboard_event(&self) {
60 self.keyboard_events.fetch_add(1, Ordering::Relaxed);
61 }
62
63 pub fn record_keyboard_events(&self, count: u64) {
65 self.keyboard_events.fetch_add(count, Ordering::Relaxed);
66 }
67
68 pub fn record_mouse_event(&self) {
70 self.mouse_events.fetch_add(1, Ordering::Relaxed);
71 }
72
73 pub fn record_mouse_events(&self, count: u64) {
75 self.mouse_events.fetch_add(count, Ordering::Relaxed);
76 }
77
78 pub fn record_shortcut_event(&self) {
80 self.shortcut_events.fetch_add(1, Ordering::Relaxed);
81 }
82
83 pub fn record_window_completed(&self) {
85 self.windows_completed.fetch_add(1, Ordering::Relaxed);
86 }
87
88 pub fn record_snapshot_exported(&self) {
90 self.snapshots_exported.fetch_add(1, Ordering::Relaxed);
91 }
92
93 pub fn stats(&self) -> TransparencyStats {
95 TransparencyStats {
96 keyboard_events: self.keyboard_events.load(Ordering::Relaxed),
97 mouse_events: self.mouse_events.load(Ordering::Relaxed),
98 shortcut_events: self.shortcut_events.load(Ordering::Relaxed),
99 windows_completed: self.windows_completed.load(Ordering::Relaxed),
100 snapshots_exported: self.snapshots_exported.load(Ordering::Relaxed),
101 session_start: self.session_start,
102 session_duration_secs: (Utc::now() - self.session_start).num_seconds() as u64,
103 }
104 }
105
106 pub fn summary(&self) -> String {
108 let stats = self.stats();
109 format!(
110 "Session Statistics:\n\
111 - Keyboard events processed: {}\n\
112 - Mouse events processed: {}\n\
113 - Shortcut events processed: {}\n\
114 - Windows completed: {}\n\
115 - Snapshots exported: {}\n\
116 - Session duration: {} seconds\n\
117 \n\
118 Privacy Guarantee:\n\
119 - No key content captured\n\
120 - No cursor coordinates captured\n\
121 - Only timing, categories, and magnitude data retained",
122 stats.keyboard_events,
123 stats.mouse_events,
124 stats.shortcut_events,
125 stats.windows_completed,
126 stats.snapshots_exported,
127 stats.session_duration_secs
128 )
129 }
130
131 pub fn save(&self) -> Result<(), std::io::Error> {
133 if let Some(ref path) = self.persist_path {
134 if let Some(parent) = path.parent() {
136 std::fs::create_dir_all(parent)?;
137 }
138
139 let stats = self.stats();
140 let persisted = PersistedStats {
141 keyboard_events: stats.keyboard_events,
142 mouse_events: stats.mouse_events,
143 shortcut_events: stats.shortcut_events,
144 windows_completed: stats.windows_completed,
145 snapshots_exported: stats.snapshots_exported,
146 last_updated: Utc::now(),
147 };
148
149 let json = serde_json::to_string_pretty(&persisted)
150 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
151
152 std::fs::write(path, json)?;
153 }
154 Ok(())
155 }
156
157 fn load(&mut self) -> Result<(), std::io::Error> {
159 if let Some(ref path) = self.persist_path {
160 if path.exists() {
161 let content = std::fs::read_to_string(path)?;
162 let persisted: PersistedStats = serde_json::from_str(&content)
163 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
164
165 self.keyboard_events
166 .store(persisted.keyboard_events, Ordering::Relaxed);
167 self.mouse_events
168 .store(persisted.mouse_events, Ordering::Relaxed);
169 self.shortcut_events
170 .store(persisted.shortcut_events, Ordering::Relaxed);
171 self.windows_completed
172 .store(persisted.windows_completed, Ordering::Relaxed);
173 self.snapshots_exported
174 .store(persisted.snapshots_exported, Ordering::Relaxed);
175 }
176 }
177 Ok(())
178 }
179
180 pub fn reset(&self) {
182 self.keyboard_events.store(0, Ordering::Relaxed);
183 self.mouse_events.store(0, Ordering::Relaxed);
184 self.shortcut_events.store(0, Ordering::Relaxed);
185 self.windows_completed.store(0, Ordering::Relaxed);
186 self.snapshots_exported.store(0, Ordering::Relaxed);
187 }
188}
189
190impl Default for TransparencyLog {
191 fn default() -> Self {
192 Self::new()
193 }
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct TransparencyStats {
199 pub keyboard_events: u64,
200 pub mouse_events: u64,
201 pub shortcut_events: u64,
202 pub windows_completed: u64,
203 pub snapshots_exported: u64,
204 pub session_start: DateTime<Utc>,
205 pub session_duration_secs: u64,
206}
207
208#[derive(Debug, Serialize, Deserialize)]
210struct PersistedStats {
211 keyboard_events: u64,
212 mouse_events: u64,
213 #[serde(default)]
214 shortcut_events: u64,
215 windows_completed: u64,
216 snapshots_exported: u64,
217 last_updated: DateTime<Utc>,
218}
219
220pub type SharedTransparencyLog = Arc<TransparencyLog>;
222
223pub fn create_shared_log() -> SharedTransparencyLog {
225 Arc::new(TransparencyLog::new())
226}
227
228pub fn create_shared_log_with_persistence(path: PathBuf) -> SharedTransparencyLog {
230 Arc::new(TransparencyLog::with_persistence(path))
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn test_transparency_log_counting() {
239 let log = TransparencyLog::new();
240
241 log.record_keyboard_event();
242 log.record_keyboard_event();
243 log.record_mouse_event();
244
245 let stats = log.stats();
246 assert_eq!(stats.keyboard_events, 2);
247 assert_eq!(stats.mouse_events, 1);
248 }
249
250 #[test]
251 fn test_transparency_log_reset() {
252 let log = TransparencyLog::new();
253
254 log.record_keyboard_events(100);
255 log.record_mouse_events(50);
256 log.reset();
257
258 let stats = log.stats();
259 assert_eq!(stats.keyboard_events, 0);
260 assert_eq!(stats.mouse_events, 0);
261 }
262
263 #[test]
264 fn test_summary_format() {
265 let log = TransparencyLog::new();
266 let summary = log.summary();
267
268 assert!(summary.contains("Keyboard events"));
269 assert!(summary.contains("Mouse events"));
270 assert!(summary.contains("Privacy Guarantee"));
271 assert!(summary.contains("No key content captured"));
272 }
273}