use std::fs::OpenOptions;
use std::io::Write;
use super::active::LockGuard;
use super::event::{self, SessionEvent};
use super::layout;
pub fn append(slug: &str, ev: &SessionEvent) -> std::io::Result<()> {
let _guard = LockGuard::exclusive(layout::session_jsonl_lock(slug))?;
let path = layout::session_jsonl(slug);
let mut f = OpenOptions::new().create(true).append(true).open(&path)?;
let line = serde_json::to_string(ev)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
f.write_all(line.as_bytes())?;
f.write_all(b"\n")?;
f.sync_data()?;
Ok(())
}
pub fn read_all(slug: &str) -> std::io::Result<Vec<SessionEvent>> {
event::read_events(&layout::session_jsonl(slug))
}
pub fn next_raw_index(events: &[SessionEvent]) -> u32 {
events
.iter()
.filter(|e| matches!(e, SessionEvent::SourceAttempted { .. }))
.count() as u32
+ 1
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Utc;
#[test]
fn next_raw_index_counts_attempts() {
use super::super::event::RouteDecision;
let attempt = SessionEvent::SourceAttempted {
timestamp: Utc::now(),
url: "https://example.com".into(),
route_decision: RouteDecision {
executor: "postagent".into(),
kind: "hn-item".into(),
command_template: "...".into(),
composite: None,
},
note: None,
};
assert_eq!(next_raw_index(&[]), 1);
assert_eq!(next_raw_index(std::slice::from_ref(&attempt)), 2);
assert_eq!(next_raw_index(&[attempt.clone(), attempt.clone()]), 3);
}
}