use crate::config::ConfigManager;
use crate::error::{MinoError, MinoResult};
use crate::session::state::{Session, SessionStatus};
use chrono::{Duration, Utc};
use tracing::{debug, warn};
pub struct SessionManager;
impl SessionManager {
pub async fn new() -> MinoResult<Self> {
ConfigManager::ensure_state_dirs().await?;
Ok(Self)
}
pub async fn create(&self, session: &Session) -> MinoResult<()> {
session.create_file().await?;
debug!("Created session: {}", session.name);
Ok(())
}
pub async fn get(&self, name: &str) -> MinoResult<Option<Session>> {
Session::load(name).await
}
pub async fn list(&self) -> MinoResult<Vec<Session>> {
Session::list_all().await
}
pub async fn update_status(&self, name: &str, status: SessionStatus) -> MinoResult<()> {
let mut session = self
.get(name)
.await?
.ok_or_else(|| MinoError::SessionNotFound(name.to_string()))?;
session.status = status;
session.updated_at = Utc::now();
session.save().await?;
debug!("Updated session {} status to {:?}", name, status);
Ok(())
}
pub async fn set_container_id(&self, name: &str, container_id: &str) -> MinoResult<()> {
let mut session = self
.get(name)
.await?
.ok_or_else(|| MinoError::SessionNotFound(name.to_string()))?;
session.container_id = Some(container_id.to_string());
session.updated_at = Utc::now();
session.save().await?;
debug!("Set container ID for session {}: {}", name, container_id);
Ok(())
}
pub async fn delete(&self, name: &str) -> MinoResult<()> {
let session = self
.get(name)
.await?
.ok_or_else(|| MinoError::SessionNotFound(name.to_string()))?;
session.delete().await?;
debug!("Deleted session: {}", name);
Ok(())
}
pub async fn find_by_container(&self, container_id: &str) -> MinoResult<Option<Session>> {
let sessions = self.list().await?;
Ok(sessions
.into_iter()
.find(|s| s.container_id.as_deref() == Some(container_id)))
}
pub async fn cleanup(&self, max_age_hours: u32) -> MinoResult<u32> {
if max_age_hours == 0 {
return Ok(0);
}
let cutoff = Utc::now() - Duration::hours(max_age_hours as i64);
let sessions = self.list().await?;
let mut cleaned = 0u32;
for session in sessions {
let dominated = matches!(
session.status,
SessionStatus::Stopped | SessionStatus::Failed
);
if dominated && session.updated_at < cutoff {
match session.delete().await {
Ok(()) => {
debug!("Cleaned up session: {}", session.name);
cleaned += 1;
}
Err(e) => {
warn!("Failed to clean up session {}: {}", session.name, e);
}
}
}
}
Ok(cleaned)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn session_status_transitions() {
let status = SessionStatus::Starting;
assert_eq!(status, SessionStatus::Starting);
let status = SessionStatus::Running;
assert_eq!(status, SessionStatus::Running);
let status = SessionStatus::Stopped;
assert_eq!(status, SessionStatus::Stopped);
}
}