use std::path::{Path, PathBuf};
use crate::core::session::{Session, SessionId};
pub fn pause_path_in(base: &Path, id: &SessionId) -> PathBuf {
base.join(".trusty-mpm")
.join("sessions")
.join(id.0.to_string())
.join("pause.json")
}
pub fn pause_path(id: &SessionId) -> PathBuf {
pause_path_in(&dirs::home_dir().unwrap_or_default(), id)
}
pub fn save_pause_in(base: &Path, session: &Session) -> std::io::Result<()> {
let path = pause_path_in(base, &session.id);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let paused_at: chrono::DateTime<chrono::Utc> = session
.paused_at
.unwrap_or_else(std::time::SystemTime::now)
.into();
let record = serde_json::json!({
"paused_at": paused_at.to_rfc3339(),
"summary": session.pause_summary,
"session_id": session.id.0.to_string(),
});
let json = serde_json::to_string_pretty(&record).map_err(std::io::Error::other)?;
std::fs::write(&path, json)
}
pub fn save_pause(session: &Session) -> std::io::Result<()> {
save_pause_in(&dirs::home_dir().unwrap_or_default(), session)
}
pub fn load_pause_in(base: &Path, id: &SessionId) -> Option<serde_json::Value> {
let bytes = std::fs::read(pause_path_in(base, id)).ok()?;
serde_json::from_slice(&bytes).ok()
}
pub fn load_pause(id: &SessionId) -> Option<serde_json::Value> {
load_pause_in(&dirs::home_dir().unwrap_or_default(), id)
}
pub fn clear_pause_in(base: &Path, id: &SessionId) -> std::io::Result<()> {
match std::fs::remove_file(pause_path_in(base, id)) {
Ok(()) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(e),
}
}
pub fn clear_pause(id: &SessionId) -> std::io::Result<()> {
clear_pause_in(&dirs::home_dir().unwrap_or_default(), id)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::session::{ControlModel, SessionStatus};
#[test]
fn pause_path_in_layout() {
let id = SessionId::new();
let path = pause_path_in(Path::new("/home/op"), &id);
assert!(path.ends_with("pause.json"));
assert!(path.to_string_lossy().contains(".trusty-mpm"));
assert!(path.to_string_lossy().contains(&id.0.to_string()));
}
#[test]
fn pause_path_is_under_home() {
let id = SessionId::new();
let path = pause_path(&id);
assert!(path.ends_with("pause.json"));
assert!(path.to_string_lossy().contains(".trusty-mpm"));
}
#[test]
fn load_missing_returns_none() {
let tmp = tempfile::tempdir().expect("temp dir");
assert!(load_pause_in(tmp.path(), &SessionId::new()).is_none());
}
#[test]
fn save_then_load_round_trips() {
let tmp = tempfile::tempdir().expect("temp dir");
let mut session = Session::new(SessionId::new(), "/tmp/p", ControlModel::Tmux, None);
session.status = SessionStatus::Paused;
session.paused_at = Some(std::time::SystemTime::now());
session.pause_summary = Some("stopped to grab coffee".to_string());
save_pause_in(tmp.path(), &session).expect("save pause");
let loaded = load_pause_in(tmp.path(), &session.id).expect("pause file exists");
assert_eq!(loaded["session_id"], session.id.0.to_string());
assert_eq!(loaded["summary"], "stopped to grab coffee");
assert!(loaded["paused_at"].as_str().is_some());
}
#[test]
fn save_with_no_summary_writes_null() {
let tmp = tempfile::tempdir().expect("temp dir");
let session = Session::new(SessionId::new(), "/tmp/p", ControlModel::Tmux, None);
save_pause_in(tmp.path(), &session).expect("save pause");
let loaded = load_pause_in(tmp.path(), &session.id).expect("pause file exists");
assert!(loaded["summary"].is_null());
}
#[test]
fn clear_removes_file() {
let tmp = tempfile::tempdir().expect("temp dir");
let session = Session::new(SessionId::new(), "/tmp/p", ControlModel::Tmux, None);
save_pause_in(tmp.path(), &session).expect("save pause");
assert!(load_pause_in(tmp.path(), &session.id).is_some());
clear_pause_in(tmp.path(), &session.id).expect("clear pause");
assert!(load_pause_in(tmp.path(), &session.id).is_none());
}
#[test]
fn clear_missing_is_ok() {
let tmp = tempfile::tempdir().expect("temp dir");
clear_pause_in(tmp.path(), &SessionId::new()).expect("clear is idempotent");
}
}