use crate::agent::session::{
SessionInfo, delete_session as delete_session_file, fork_session, load_session_info,
};
use std::path::{Path, PathBuf};
pub trait SessionRepo {
fn list(
&self,
session_dir: &Path,
filter_cwd: Option<&Path>,
progress: Option<&dyn Fn(usize, usize)>,
) -> Vec<SessionInfo>;
fn list_all(&self, progress: Option<&dyn Fn(usize, usize)>) -> Vec<SessionInfo>;
fn delete(&self, path: &Path) -> std::io::Result<()>;
fn fork(
&self,
source_path: &Path,
target_dir: &Path,
entry_id: Option<&str>,
position: Option<&str>,
) -> std::io::Result<String>;
fn load_info(&self, path: &Path) -> Option<SessionInfo>;
}
pub struct DefaultSessionRepo;
impl Default for DefaultSessionRepo {
fn default() -> Self {
Self
}
}
impl DefaultSessionRepo {
pub fn new() -> Self {
Self
}
}
impl SessionRepo for DefaultSessionRepo {
fn list(
&self,
session_dir: &Path,
filter_cwd: Option<&Path>,
progress: Option<&dyn Fn(usize, usize)>,
) -> Vec<SessionInfo> {
list_sessions_with_progress(session_dir, filter_cwd, progress, 1)
}
fn list_all(&self, progress: Option<&dyn Fn(usize, usize)>) -> Vec<SessionInfo> {
let dir = directories::BaseDirs::new()
.map(|d| d.home_dir().join(".rab").join("sessions"))
.unwrap_or_else(|| PathBuf::from("/tmp/.rab/sessions"));
let mut all_sessions: Vec<SessionInfo> = Vec::new();
let mut dirs = vec![dir.clone()];
if let Ok(read_dir) = std::fs::read_dir(&dir) {
for entry in read_dir.flatten() {
let path = entry.path();
if path.is_dir() {
dirs.push(path);
}
}
}
let total_dirs = dirs.len();
let mut loaded = 0;
for session_dir in &dirs {
let sessions = list_sessions_with_progress(session_dir, None, progress, 1);
loaded += 1;
if let Some(ref cb) = progress {
cb(loaded, total_dirs);
}
all_sessions.extend(sessions);
}
all_sessions.sort_by_key(|b| std::cmp::Reverse(b.created));
all_sessions
}
fn delete(&self, path: &Path) -> std::io::Result<()> {
delete_session_file(path)
}
fn fork(
&self,
source_path: &Path,
target_dir: &Path,
entry_id: Option<&str>,
position: Option<&str>,
) -> std::io::Result<String> {
fork_session(source_path, target_dir, entry_id, position)
}
fn load_info(&self, path: &Path) -> Option<SessionInfo> {
load_session_info(path)
}
}
fn list_sessions_with_progress(
session_dir: &Path,
filter_cwd: Option<&Path>,
progress: Option<&dyn Fn(usize, usize)>,
_concurrency: usize,
) -> Vec<SessionInfo> {
let dir = match std::fs::read_dir(session_dir) {
Ok(d) => d,
Err(_) => return vec![],
};
let file_paths: Vec<PathBuf> = dir
.flatten()
.filter(|e| e.path().extension().is_some_and(|ext| ext == "jsonl"))
.map(|e| e.path())
.collect();
let total = file_paths.len();
let mut sessions: Vec<SessionInfo> = Vec::with_capacity(total);
let mut loaded = 0;
for path in &file_paths {
let header = crate::agent::session::read_session_header(path);
if let Some(ref h) = header
&& let Some(filter) = filter_cwd
&& h.cwd != filter.to_string_lossy().as_ref()
{
loaded += 1;
if let Some(ref cb) = progress {
cb(loaded, total);
}
continue;
}
if let Some(info) = load_session_info(path) {
sessions.push(info);
}
loaded += 1;
if let Some(ref cb) = progress {
cb(loaded, total);
}
}
sessions.sort_by_key(|b| std::cmp::Reverse(b.created));
sessions
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_list_empty_dir() {
let repo = DefaultSessionRepo::new();
let tmp = TempDir::new().unwrap();
let sessions = repo.list(tmp.path(), None, None);
assert!(sessions.is_empty());
}
}