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 shortcut_events: AtomicU64,
21 session_start: DateTime<Utc>,
23 persist_path: Option<PathBuf>,
25}
26
27impl TransparencyLog {
28 pub fn new() -> Self {
30 Self {
31 keyboard_events: AtomicU64::new(0),
32 mouse_events: AtomicU64::new(0),
33 shortcut_events: AtomicU64::new(0),
34 session_start: Utc::now(),
35 persist_path: None,
36 }
37 }
38
39 pub fn with_persistence(path: PathBuf) -> Self {
41 let mut log = Self::new();
42 log.persist_path = Some(path);
43
44 if let Err(e) = log.load() {
46 eprintln!("Note: Could not load previous transparency stats: {e}");
47 }
48
49 log
50 }
51
52 pub fn record_keyboard_event(&self) {
54 self.keyboard_events.fetch_add(1, Ordering::Relaxed);
55 }
56
57 pub fn record_keyboard_events(&self, count: u64) {
59 self.keyboard_events.fetch_add(count, Ordering::Relaxed);
60 }
61
62 pub fn record_mouse_event(&self) {
64 self.mouse_events.fetch_add(1, Ordering::Relaxed);
65 }
66
67 pub fn record_mouse_events(&self, count: u64) {
69 self.mouse_events.fetch_add(count, Ordering::Relaxed);
70 }
71
72 pub fn record_shortcut_event(&self) {
74 self.shortcut_events.fetch_add(1, Ordering::Relaxed);
75 }
76
77 pub fn stats(&self) -> TransparencyStats {
79 TransparencyStats {
80 keyboard_events: self.keyboard_events.load(Ordering::Relaxed),
81 mouse_events: self.mouse_events.load(Ordering::Relaxed),
82 shortcut_events: self.shortcut_events.load(Ordering::Relaxed),
83 session_start: self.session_start,
84 session_duration_secs: (Utc::now() - self.session_start).num_seconds() as u64,
85 }
86 }
87
88 pub fn summary(&self) -> String {
90 let stats = self.stats();
91 format!(
92 "Session Statistics:\n\
93 - Keyboard events processed: {}\n\
94 - Mouse events processed: {}\n\
95 - Shortcut events processed: {}\n\
96 - Session duration: {} seconds\n\
97 \n\
98 Privacy Guarantee:\n\
99 - No key content captured\n\
100 - No cursor coordinates captured\n\
101 - Only timing, categories, and magnitude data retained",
102 stats.keyboard_events,
103 stats.mouse_events,
104 stats.shortcut_events,
105 stats.session_duration_secs
106 )
107 }
108
109 pub fn save(&self) -> Result<(), std::io::Error> {
111 if let Some(ref path) = self.persist_path {
112 if let Some(parent) = path.parent() {
114 std::fs::create_dir_all(parent)?;
115 }
116
117 let stats = self.stats();
118 let persisted = PersistedStats {
119 keyboard_events: stats.keyboard_events,
120 mouse_events: stats.mouse_events,
121 shortcut_events: stats.shortcut_events,
122 last_updated: Utc::now(),
123 };
124
125 let json = serde_json::to_string_pretty(&persisted).map_err(std::io::Error::other)?;
126
127 std::fs::write(path, json)?;
128 }
129 Ok(())
130 }
131
132 fn load(&mut self) -> Result<(), std::io::Error> {
134 if let Some(ref path) = self.persist_path {
135 if path.exists() {
136 let content = std::fs::read_to_string(path)?;
137 let persisted: PersistedStats =
138 serde_json::from_str(&content).map_err(std::io::Error::other)?;
139
140 self.keyboard_events
141 .store(persisted.keyboard_events, Ordering::Relaxed);
142 self.mouse_events
143 .store(persisted.mouse_events, Ordering::Relaxed);
144 self.shortcut_events
145 .store(persisted.shortcut_events, Ordering::Relaxed);
146 }
147 }
148 Ok(())
149 }
150
151 pub fn reset(&self) {
153 self.keyboard_events.store(0, Ordering::Relaxed);
154 self.mouse_events.store(0, Ordering::Relaxed);
155 self.shortcut_events.store(0, Ordering::Relaxed);
156 }
157}
158
159impl Default for TransparencyLog {
160 fn default() -> Self {
161 Self::new()
162 }
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct TransparencyStats {
168 pub keyboard_events: u64,
170 pub mouse_events: u64,
172 pub shortcut_events: u64,
174 pub session_start: DateTime<Utc>,
176 pub session_duration_secs: u64,
178}
179
180#[derive(Debug, Serialize, Deserialize)]
182struct PersistedStats {
183 keyboard_events: u64,
184 mouse_events: u64,
185 #[serde(default)]
186 shortcut_events: u64,
187 last_updated: DateTime<Utc>,
188}
189
190pub type SharedTransparencyLog = Arc<TransparencyLog>;
192
193pub fn create_shared_log() -> SharedTransparencyLog {
195 Arc::new(TransparencyLog::new())
196}
197
198pub fn create_shared_log_with_persistence(path: PathBuf) -> SharedTransparencyLog {
200 Arc::new(TransparencyLog::with_persistence(path))
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn test_transparency_log_counting() {
209 let log = TransparencyLog::new();
210
211 log.record_keyboard_event();
212 log.record_keyboard_event();
213 log.record_mouse_event();
214
215 let stats = log.stats();
216 assert_eq!(stats.keyboard_events, 2);
217 assert_eq!(stats.mouse_events, 1);
218 }
219
220 #[test]
221 fn test_transparency_log_reset() {
222 let log = TransparencyLog::new();
223
224 log.record_keyboard_events(100);
225 log.record_mouse_events(50);
226 log.reset();
227
228 let stats = log.stats();
229 assert_eq!(stats.keyboard_events, 0);
230 assert_eq!(stats.mouse_events, 0);
231 }
232
233 #[test]
234 fn test_summary_format() {
235 let log = TransparencyLog::new();
236 let summary = log.summary();
237
238 assert!(summary.contains("Keyboard events"));
239 assert!(summary.contains("Mouse events"));
240 assert!(summary.contains("Privacy Guarantee"));
241 assert!(summary.contains("No key content captured"));
242 }
243}