diff --git a/Cargo.toml b/Cargo.toml index a407e45..c40c3e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,8 @@ anyhow = "1.0.86" ratatui = "0.27.0" regex = "1.10.5" shellexpand = "3.1.0" +serde = { version = "1.0", features = ["derive", "rc"] } +serde_json = "1.0" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["winuser", "processthreadsapi"] } diff --git a/src/constants.rs b/src/constants.rs index 0b53098..3791d25 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -12,6 +12,7 @@ pub const SESSION_IN_PROGRESS: &str = "Session In-Progress"; pub const SESSION_PAUSED: &str = "Session is paused. Unpause with 'p'."; pub const SESSION_STATS_TITLE: &str = "Session Stats"; pub const STATUS_FILE: &str = "~/.antidrift_status"; +pub const HISTORY_FILE: &str = "~/.antidrift_history.jsonl"; pub const STATUS_TITLE: &str = "Status"; pub const STATUS_CONFIGURE: &str = "🔄 antidrift configure session"; pub const STATUS_PAUSED: &str = "⏸ antidrift paused"; diff --git a/src/main.rs b/src/main.rs index e17b515..2b08698 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,10 @@ use anyhow::Result; +use serde::{Serialize, Serializer}; use shellexpand; use std::collections::HashMap; use std::io::{stdout, Write}; use std::rc::Rc; -use std::time::{Duration, Instant}; +use std::time::{Duration, Instant}; // <--- Add this mod constants; mod window; @@ -31,8 +32,17 @@ enum State { ShouldQuit, } +fn serialize_duration(duration: &Duration, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_u64(duration.as_secs()) +} + +#[derive(Serialize)] struct SessionResult { intention: String, + #[serde(serialize_with = "serialize_duration")] duration: Duration, session_ratings: Vec, rating: u8, @@ -67,7 +77,7 @@ impl SessionResult { } } -#[derive(Clone)] +#[derive(Clone, Serialize)] struct SessionRating { window_title: Rc, duration: Duration, @@ -91,6 +101,24 @@ struct App { last_tick_1s: Instant, } +fn save_session(session: &SessionResult) { + let path = shellexpand::tilde(constants::HISTORY_FILE).to_string(); + + // 1. Open file in append mode, create if missing + let file = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(path); + + if let Ok(mut f) = file { + // 2. Serialize to JSON + if let Ok(json) = serde_json::to_string(session) { + // 3. Write newline appended + let _ = writeln!(f, "{}", json); + } + } +} + impl App { fn new() -> Self { let window_title = window::get_title_clean(); @@ -217,7 +245,7 @@ impl App { Duration::from_millis(50).saturating_sub(self.last_tick_50ms.elapsed()) } - fn get_session_results(&self) -> Vec { + fn get_session_results(&self) -> Vec> { self.session_results .iter() .map(|r| { @@ -233,7 +261,7 @@ impl App { .collect() } - fn get_session_stats(&self) -> Vec { + fn get_session_stats(&self) -> Vec> { let mut zero_encountered = if self.state != State::End { true } else { @@ -386,6 +414,7 @@ fn handle_events(app: &mut App) -> Result<()> { rating_f64: 0.0, }; session_result.rate(); + save_session(&session_result); app.session_results.push(session_result); } }