ralph/commands/
cleanup.rs1use std::time::Duration;
17
18use anyhow::Result;
19
20use crate::cli::cleanup::CleanupArgs;
21use crate::fsutil;
22
23pub fn run(args: &CleanupArgs) -> Result<()> {
25 let retention = if args.force {
26 Duration::ZERO
27 } else {
28 crate::constants::timeouts::TEMP_RETENTION
29 };
30
31 if args.dry_run {
32 return run_dry_run(retention);
33 }
34
35 let removed = fsutil::cleanup_default_temp_dirs(retention)?;
36 println!("Cleaned up {} temp entries", removed);
37 Ok(())
38}
39
40fn run_dry_run(retention: Duration) -> Result<()> {
41 let base = fsutil::ralph_temp_root();
42 println!(
43 "Dry run - would clean temp files older than {:?}",
44 retention
45 );
46 println!("Temp directory: {}", base.display());
47
48 if !base.exists() {
49 println!("Temp directory does not exist - nothing to clean.");
50 return Ok(());
51 }
52
53 let entries = list_entries_to_clean(
54 &base,
55 &[crate::constants::paths::RALPH_TEMP_PREFIX],
56 retention,
57 )?;
58
59 if entries.is_empty() {
60 println!("No temp files or directories would be cleaned.");
61 } else {
62 println!("Would delete {} entries:", entries.len());
63 for entry in entries {
64 let entry_type = if entry.is_dir() { "[dir]" } else { "[file]" };
65 println!(" {} {}", entry_type, entry.display());
66 }
67 }
68
69 let legacy_entries = list_entries_to_clean(
71 &std::env::temp_dir(),
72 &[crate::constants::paths::LEGACY_PROMPT_PREFIX],
73 retention,
74 )?;
75
76 if !legacy_entries.is_empty() {
77 println!("\nWould delete {} legacy entries:", legacy_entries.len());
78 for entry in legacy_entries {
79 let entry_type = if entry.is_dir() { "[dir]" } else { "[file]" };
80 println!(" {} {}", entry_type, entry.display());
81 }
82 }
83
84 Ok(())
85}
86
87fn list_entries_to_clean(
88 base: &std::path::Path,
89 prefixes: &[&str],
90 retention: Duration,
91) -> Result<Vec<std::path::PathBuf>> {
92 use std::fs;
93 use std::time::SystemTime;
94
95 let mut entries = Vec::new();
96
97 if !base.exists() {
98 return Ok(entries);
99 }
100
101 let now = SystemTime::now();
102
103 for entry in fs::read_dir(base)? {
104 let entry = entry?;
105 let path = entry.path();
106 let name = entry.file_name();
107 let name = name.to_string_lossy();
108
109 if !prefixes.iter().any(|prefix| name.starts_with(prefix)) {
110 continue;
111 }
112
113 let metadata = match entry.metadata() {
114 Ok(metadata) => metadata,
115 Err(_) => continue,
116 };
117
118 let modified = match metadata.modified() {
119 Ok(time) => time,
120 Err(_) => continue,
121 };
122
123 let age = match now.duration_since(modified) {
124 Ok(age) => age,
125 Err(_) => continue,
126 };
127
128 if age >= retention {
129 entries.push(path);
130 }
131 }
132
133 Ok(entries)
134}