use std::path::{Path, PathBuf};
use std::time::SystemTime;
#[derive(Debug, Clone)]
pub struct ModifiedSession {
pub path: PathBuf,
pub mtime: SystemTime,
}
fn encode_project_path(project_path: &str) -> String {
project_path.replace('/', "-")
}
pub fn find_modified_sessions(
claude_dir: &Path,
since: Option<SystemTime>,
project_paths: &[String],
) -> Vec<ModifiedSession> {
let projects_dir = claude_dir.join("projects");
if !projects_dir.exists() {
return Vec::new();
}
let patterns: Vec<String> = if project_paths.is_empty() {
vec![format!("{}/**/*.jsonl", projects_dir.display())]
} else {
project_paths
.iter()
.map(|path| {
let encoded = encode_project_path(path);
format!("{}/{}*/**/*.jsonl", projects_dir.display(), encoded)
})
.collect()
};
let mut results = Vec::new();
for pattern in &patterns {
for entry in glob::glob(pattern).unwrap_or_else(|_| glob::glob("").unwrap()) {
if let Ok(path) = entry {
if let Ok(metadata) = std::fs::metadata(&path) {
if let Ok(mtime) = metadata.modified() {
let dominated = match since {
Some(since_time) => mtime > since_time,
None => true,
};
if dominated {
results.push(ModifiedSession { path, mtime });
}
}
}
}
}
}
results
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_encode_project_path() {
assert_eq!(
encode_project_path("/Users/iman/repos/app"),
"-Users-iman-repos-app"
);
assert_eq!(encode_project_path("/tmp/test"), "-tmp-test");
}
#[test]
fn test_find_modified_sessions_detects_new_files() {
let dir = TempDir::new().unwrap();
let sessions_dir = dir.path().join("projects").join("-tmp-my-project");
fs::create_dir_all(&sessions_dir).unwrap();
let session_file = sessions_dir.join("session1.jsonl");
fs::write(&session_file, "{}\n").unwrap();
let modified = find_modified_sessions(
dir.path(),
None,
&["/tmp/my-project".to_string()],
);
assert_eq!(modified.len(), 1);
assert!(modified[0].path.ends_with("session1.jsonl"));
}
#[test]
fn test_find_modified_sessions_skips_unregistered() {
let dir = TempDir::new().unwrap();
let registered_dir = dir.path().join("projects").join("-tmp-registered");
fs::create_dir_all(®istered_dir).unwrap();
fs::write(registered_dir.join("session1.jsonl"), "{}\n").unwrap();
let unregistered_dir = dir.path().join("projects").join("-Users-iman-Downloads-stuff");
fs::create_dir_all(&unregistered_dir).unwrap();
fs::write(unregistered_dir.join("session2.jsonl"), "{}\n").unwrap();
let modified = find_modified_sessions(
dir.path(),
None,
&["/tmp/registered".to_string()],
);
assert_eq!(modified.len(), 1);
assert!(modified[0].path.to_str().unwrap().contains("registered"));
}
#[test]
fn test_find_modified_sessions_empty_paths_scans_all() {
let dir = TempDir::new().unwrap();
let sessions_dir = dir.path().join("projects").join("any-project");
fs::create_dir_all(&sessions_dir).unwrap();
fs::write(sessions_dir.join("session1.jsonl"), "{}\n").unwrap();
let modified = find_modified_sessions(dir.path(), None, &[]);
assert_eq!(modified.len(), 1);
}
#[test]
fn test_find_modified_sessions_skips_unchanged() {
let dir = TempDir::new().unwrap();
let sessions_dir = dir.path().join("projects").join("-tmp-project");
fs::create_dir_all(&sessions_dir).unwrap();
let session_file = sessions_dir.join("session1.jsonl");
fs::write(&session_file, "{}\n").unwrap();
let mtime = fs::metadata(&session_file).unwrap().modified().unwrap();
let modified = find_modified_sessions(
dir.path(),
Some(mtime),
&["/tmp/project".to_string()],
);
assert_eq!(modified.len(), 0);
}
}