use std::path::{Path, PathBuf};
use agentic_reality::engine::RealityEngine;
use agentic_reality::format::{ArealReader, ArealWriter};
pub struct SessionManager {
pub engine: RealityEngine,
pub data_path: Option<String>,
pub workspace_id: Option<String>,
pub autosave: bool,
}
impl SessionManager {
pub fn new() -> Self {
Self {
engine: RealityEngine::new(),
data_path: None,
workspace_id: None,
autosave: false,
}
}
pub fn with_path(path: String) -> Self {
let workspace_id = derive_workspace_id(&path);
Self {
engine: RealityEngine::new(),
data_path: Some(path),
workspace_id,
autosave: false,
}
}
pub fn set_autosave(&mut self, enabled: bool) {
self.autosave = enabled;
}
pub fn is_dirty(&self) -> bool {
self.engine.is_dirty()
}
pub fn save(&mut self) -> Result<bool, SessionError> {
let path_str = match &self.data_path {
Some(p) => p.clone(),
None => return Ok(false),
};
let path = PathBuf::from(&path_str);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| SessionError::Io(e.to_string()))?;
}
ArealWriter::save(&self.engine, &path).map_err(|e| SessionError::Save(e.to_string()))?;
self.engine.mark_clean();
Ok(true)
}
pub fn load(&mut self) -> Result<bool, SessionError> {
let path_str = match &self.data_path {
Some(p) => p.clone(),
None => return Ok(false),
};
let path = PathBuf::from(&path_str);
if !path.exists() {
return Ok(false);
}
let engine = ArealReader::load(&path).map_err(|e| SessionError::Load(e.to_string()))?;
self.engine = engine;
Ok(true)
}
pub fn autosave_if_dirty(&mut self) -> Result<bool, SessionError> {
if self.autosave && self.is_dirty() {
self.save()
} else {
Ok(false)
}
}
pub fn workspace_id(&self) -> Option<&str> {
self.workspace_id.as_deref()
}
}
impl Default for SessionManager {
fn default() -> Self {
Self::new()
}
}
fn derive_workspace_id(path: &str) -> Option<String> {
let canonical = match std::fs::canonicalize(Path::new(path)) {
Ok(p) => p.to_string_lossy().to_string(),
Err(_) => path.to_string(),
};
let hash = blake3::hash(canonical.as_bytes());
let hex = hash.to_hex();
Some(hex[..16].to_string())
}
#[derive(Debug)]
pub enum SessionError {
Io(String),
Save(String),
Load(String),
}
impl std::fmt::Display for SessionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SessionError::Io(msg) => write!(f, "session IO error: {}", msg),
SessionError::Save(msg) => write!(f, "session save error: {}", msg),
SessionError::Load(msg) => write!(f, "session load error: {}", msg),
}
}
}
impl std::error::Error for SessionError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_session() {
let s = SessionManager::new();
assert!(s.data_path.is_none());
assert!(s.workspace_id.is_none());
assert!(!s.autosave);
}
#[test]
fn test_session_with_path() {
let s = SessionManager::with_path("/tmp/test.areal".into());
assert_eq!(s.data_path.as_deref(), Some("/tmp/test.areal"));
assert!(s.workspace_id.is_some());
}
#[test]
fn test_autosave_toggle() {
let mut s = SessionManager::new();
assert!(!s.autosave);
s.set_autosave(true);
assert!(s.autosave);
}
#[test]
fn test_save_no_path_returns_false() {
let mut s = SessionManager::new();
let result = s.save();
assert!(result.is_ok());
match result {
Ok(saved) => assert!(!saved),
Err(_) => panic!("expected Ok(false)"),
}
}
#[test]
fn test_load_no_path_returns_false() {
let mut s = SessionManager::new();
let result = s.load();
assert!(result.is_ok());
match result {
Ok(loaded) => assert!(!loaded),
Err(_) => panic!("expected Ok(false)"),
}
}
#[test]
fn test_workspace_id_derivation() {
let id = derive_workspace_id("/tmp/test.areal");
assert!(id.is_some());
match id {
Some(ref s) => assert_eq!(s.len(), 16),
None => panic!("expected Some"),
}
}
#[test]
fn test_session_error_display() {
let e = SessionError::Save("disk full".into());
assert!(e.to_string().contains("disk full"));
}
#[test]
fn test_default() {
let s = SessionManager::default();
assert!(s.data_path.is_none());
}
#[test]
fn test_autosave_if_dirty_not_dirty() {
let mut s = SessionManager::new();
s.set_autosave(true);
let result = s.autosave_if_dirty();
assert!(result.is_ok());
match result {
Ok(saved) => assert!(!saved),
Err(_) => panic!("expected Ok(false)"),
}
}
}