vtcode_memory/
retention.rs1use std::path::Path;
4use std::time::{Duration, SystemTime, UNIX_EPOCH};
5
6use walkdir::WalkDir;
7
8use crate::error::SessionStoreError;
9use crate::query::SessionSummary;
10use crate::sessions_root;
11
12#[derive(Debug, Clone, Copy)]
14pub struct RetentionPolicy {
15 pub max_sessions: usize,
17 pub max_age_days: u64,
19}
20
21impl Default for RetentionPolicy {
22 fn default() -> Self {
23 Self { max_sessions: 50, max_age_days: 30 }
24 }
25}
26
27pub fn apply_retention(workspace: &Path, policy: RetentionPolicy) -> Result<usize, SessionStoreError> {
33 let root = sessions_root(workspace);
34 if !root.exists() {
35 return Ok(0);
36 }
37 let mut sessions: Vec<SessionSummary> = crate::query::recent_sessions(workspace, usize::MAX);
38 let mut removed = 0usize;
39
40 if sessions.len() > policy.max_sessions {
42 sessions.sort_by(|a, b| a.updated_at.cmp(&b.updated_at));
43 let to_remove = sessions.len() - policy.max_sessions;
44 for s in sessions.iter().take(to_remove) {
45 remove_session(&root.join(&s.session_id))?;
46 removed += 1;
47 }
48 sessions.drain(..to_remove);
50 }
51
52 let cutoff = age_cutoff(policy.max_age_days);
54 for s in &sessions {
55 if older_than(s.updated_at.as_str(), cutoff) {
56 remove_session(&root.join(&s.session_id))?;
57 removed += 1;
58 }
59 }
60
61 Ok(removed)
62}
63
64pub fn gc_legacy(workspace: &Path) -> Result<u64, SessionStoreError> {
71 let vt = workspace.join(".vtcode");
72 let mut freed = 0u64;
73 for name in ["history", "logs"] {
74 let dir = vt.join(name);
75 if dir.exists() {
76 freed += dir_size(&dir);
77 std::fs::remove_dir_all(&dir).map_err(|e| SessionStoreError::io(dir.clone(), e))?;
78 }
79 }
80 Ok(freed)
81}
82
83fn remove_session(dir: &Path) -> Result<(), SessionStoreError> {
84 if dir.exists() {
85 std::fs::remove_dir_all(dir).map_err(|e| SessionStoreError::io(dir.to_path_buf(), e))?;
86 }
87 Ok(())
88}
89
90fn dir_size(dir: &Path) -> u64 {
91 WalkDir::new(dir)
92 .into_iter()
93 .filter_map(Result::ok)
94 .filter(|e| e.file_type().is_file())
95 .filter_map(|e| e.metadata().ok())
96 .map(|m| m.len())
97 .sum()
98}
99
100fn age_cutoff(max_age_days: u64) -> SystemTime {
101 SystemTime::now() - Duration::from_secs(max_age_days * 24 * 3600)
102}
103
104fn older_than(rfc3339: &str, cutoff: SystemTime) -> bool {
105 let Ok(dt) = chrono::DateTime::parse_from_rfc3339(rfc3339) else {
106 return false;
107 };
108 let cutoff_secs = cutoff
109 .duration_since(UNIX_EPOCH)
110 .map(|d| d.as_secs() as i64)
111 .unwrap_or(i64::MAX);
112 dt.timestamp() < cutoff_secs
113}