use std::sync::Arc;
use chrono::Utc;
use dashmap::DashMap;
use uuid::Uuid;
use crate::{
error::{ClawDBError, ClawDBResult},
session::context::SessionContext,
};
#[derive(Debug, Clone)]
pub struct SessionStore {
sessions: Arc<DashMap<Uuid, SessionContext>>,
}
impl SessionStore {
pub fn new() -> Self {
Self {
sessions: Arc::new(DashMap::new()),
}
}
pub fn insert(&self, ctx: SessionContext) {
self.sessions.insert(ctx.session_id, ctx);
}
#[inline]
pub fn put(&self, ctx: SessionContext) {
self.insert(ctx);
}
pub fn get(&self, session_id: Uuid) -> ClawDBResult<SessionContext> {
let ctx = self
.sessions
.get(&session_id)
.ok_or(ClawDBError::SessionNotFound(session_id))?
.clone();
if !ctx.is_valid() {
return Err(ClawDBError::SessionExpired(session_id));
}
Ok(ctx)
}
pub fn remove(&self, session_id: Uuid) -> Option<SessionContext> {
self.sessions.remove(&session_id).map(|(_, v)| v)
}
pub fn list_for_agent(&self, agent_id: Uuid) -> Vec<SessionContext> {
let now = Utc::now().timestamp();
self.sessions
.iter()
.filter(|e| e.agent_id == agent_id && e.expires_at > now)
.map(|e| e.clone())
.collect()
}
pub fn ids_for_agent(&self, agent_id: Uuid) -> Vec<Uuid> {
self.sessions
.iter()
.filter(|e| e.agent_id == agent_id)
.map(|e| e.session_id)
.collect()
}
pub fn purge_expired(&self) -> usize {
let now = Utc::now().timestamp();
let before = self.sessions.len();
self.sessions.retain(|_, ctx| ctx.expires_at > now);
before - self.sessions.len()
}
pub fn count(&self) -> usize {
self.sessions.len()
}
pub fn live_count(&self) -> usize {
let now = Utc::now().timestamp();
self.sessions.iter().filter(|e| e.expires_at > now).count()
}
pub fn persist_to(&self, path: &std::path::Path) -> ClawDBResult<()> {
let now = Utc::now().timestamp();
let live: Vec<&SessionContext> = self
.sessions
.iter()
.filter(|e| e.expires_at > now)
.map(|e| {
unsafe { &*(e.value() as *const SessionContext) }
})
.collect();
let json = serde_json::to_vec(&live)?;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(path, json)?;
Ok(())
}
pub fn restore_from(&self, path: &std::path::Path) -> ClawDBResult<usize> {
if !path.exists() {
return Ok(0);
}
let data = std::fs::read(path)?;
let sessions: Vec<SessionContext> = serde_json::from_slice(&data)?;
let now = Utc::now().timestamp();
let mut loaded = 0usize;
for ctx in sessions {
if ctx.expires_at > now {
self.sessions.insert(ctx.session_id, ctx);
loaded += 1;
}
}
Ok(loaded)
}
}
impl Default for SessionStore {
fn default() -> Self {
Self::new()
}
}
}
impl Default for SessionStore {
fn default() -> Self {
Self::new()
}
}