Compare commits
2 Commits
d84b83c6d5
...
a44d52b402
| Author | SHA1 | Date | |
|---|---|---|---|
| a44d52b402 | |||
| 1a89906ae8 |
@@ -8,6 +8,8 @@ anyhow = "1.0.86"
|
|||||||
ratatui = "0.27.0"
|
ratatui = "0.27.0"
|
||||||
regex = "1.10.5"
|
regex = "1.10.5"
|
||||||
shellexpand = "3.1.0"
|
shellexpand = "3.1.0"
|
||||||
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = { version = "0.3", features = ["winuser", "processthreadsapi"] }
|
winapi = { version = "0.3", features = ["winuser", "processthreadsapi"] }
|
||||||
|
|||||||
@@ -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_PAUSED: &str = "Session is paused. Unpause with 'p'.";
|
||||||
pub const SESSION_STATS_TITLE: &str = "Session Stats";
|
pub const SESSION_STATS_TITLE: &str = "Session Stats";
|
||||||
pub const STATUS_FILE: &str = "~/.antidrift_status";
|
pub const STATUS_FILE: &str = "~/.antidrift_status";
|
||||||
|
pub const HISTORY_FILE: &str = "~/.antidrift_history.jsonl";
|
||||||
pub const STATUS_TITLE: &str = "Status";
|
pub const STATUS_TITLE: &str = "Status";
|
||||||
pub const STATUS_CONFIGURE: &str = "🔄 antidrift configure session";
|
pub const STATUS_CONFIGURE: &str = "🔄 antidrift configure session";
|
||||||
pub const STATUS_PAUSED: &str = "⏸ antidrift paused";
|
pub const STATUS_PAUSED: &str = "⏸ antidrift paused";
|
||||||
|
|||||||
38
src/main.rs
38
src/main.rs
@@ -1,9 +1,10 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use serde::{Serialize, Serializer};
|
||||||
use shellexpand;
|
use shellexpand;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::{stdout, Write};
|
use std::io::{stdout, Write};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant}; // <--- Add this
|
||||||
mod constants;
|
mod constants;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
||||||
@@ -31,8 +32,17 @@ enum State {
|
|||||||
ShouldQuit,
|
ShouldQuit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serialize_duration<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_u64(duration.as_secs())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
struct SessionResult {
|
struct SessionResult {
|
||||||
intention: String,
|
intention: String,
|
||||||
|
#[serde(serialize_with = "serialize_duration")]
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
session_ratings: Vec<SessionRating>,
|
session_ratings: Vec<SessionRating>,
|
||||||
rating: u8,
|
rating: u8,
|
||||||
@@ -67,7 +77,7 @@ impl SessionResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Serialize)]
|
||||||
struct SessionRating {
|
struct SessionRating {
|
||||||
window_title: Rc<String>,
|
window_title: Rc<String>,
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
@@ -91,6 +101,24 @@ struct App {
|
|||||||
last_tick_1s: Instant,
|
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 {
|
impl App {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
let window_title = window::get_title_clean();
|
let window_title = window::get_title_clean();
|
||||||
@@ -217,7 +245,7 @@ impl App {
|
|||||||
Duration::from_millis(50).saturating_sub(self.last_tick_50ms.elapsed())
|
Duration::from_millis(50).saturating_sub(self.last_tick_50ms.elapsed())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_session_results(&self) -> Vec<Line> {
|
fn get_session_results(&self) -> Vec<Line<'_>> {
|
||||||
self.session_results
|
self.session_results
|
||||||
.iter()
|
.iter()
|
||||||
.map(|r| {
|
.map(|r| {
|
||||||
@@ -233,7 +261,7 @@ impl App {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_session_stats(&self) -> Vec<Line> {
|
fn get_session_stats(&self) -> Vec<Line<'_>> {
|
||||||
let mut zero_encountered = if self.state != State::End {
|
let mut zero_encountered = if self.state != State::End {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -273,6 +301,7 @@ fn duration_as_str(duration: &Duration) -> String {
|
|||||||
fn session_stats_as_vec(session_stats: &HashMap<Rc<String>, Duration>) -> Vec<SessionRating> {
|
fn session_stats_as_vec(session_stats: &HashMap<Rc<String>, Duration>) -> Vec<SessionRating> {
|
||||||
let mut stats: Vec<_> = session_stats
|
let mut stats: Vec<_> = session_stats
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|(_, duration)| duration.as_secs() > 30)
|
||||||
.map(|(title, duration)| SessionRating {
|
.map(|(title, duration)| SessionRating {
|
||||||
window_title: title.clone(),
|
window_title: title.clone(),
|
||||||
duration: duration.clone(),
|
duration: duration.clone(),
|
||||||
@@ -386,6 +415,7 @@ fn handle_events(app: &mut App) -> Result<()> {
|
|||||||
rating_f64: 0.0,
|
rating_f64: 0.0,
|
||||||
};
|
};
|
||||||
session_result.rate();
|
session_result.rate();
|
||||||
|
save_session(&session_result);
|
||||||
app.session_results.push(session_result);
|
app.session_results.push(session_result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user