1use crate::constants::paths::{LEGACY_PROMPT_PREFIX, RALPH_TEMP_DIR_NAME, RALPH_TEMP_PREFIX};
20use anyhow::{Context, Result};
21use std::fs;
22use std::path::{Path, PathBuf};
23use std::time::{Duration, SystemTime};
24
25pub fn ralph_temp_root() -> PathBuf {
26 std::env::temp_dir().join(RALPH_TEMP_DIR_NAME)
27}
28
29pub fn cleanup_stale_temp_entries(
30 base: &Path,
31 prefixes: &[&str],
32 retention: Duration,
33) -> Result<usize> {
34 if !base.exists() {
35 return Ok(0);
36 }
37
38 let now = SystemTime::now();
39 let mut removed = 0usize;
40
41 for entry in fs::read_dir(base).with_context(|| format!("read temp dir {}", base.display()))? {
42 let entry = entry.with_context(|| format!("read temp dir entry in {}", base.display()))?;
43 let path = entry.path();
44 let name = entry.file_name();
45 let name = name.to_string_lossy();
46
47 if !prefixes.iter().any(|prefix| name.starts_with(prefix)) {
48 continue;
49 }
50
51 let metadata = match entry.metadata() {
52 Ok(metadata) => metadata,
53 Err(err) => {
54 log::warn!(
55 "unable to read temp metadata for {}: {}",
56 path.display(),
57 err
58 );
59 continue;
60 }
61 };
62
63 let modified = match metadata.modified() {
64 Ok(time) => time,
65 Err(err) => {
66 log::warn!(
67 "unable to read temp modified time for {}: {}",
68 path.display(),
69 err
70 );
71 continue;
72 }
73 };
74
75 let age = match now.duration_since(modified) {
76 Ok(age) => age,
77 Err(_) => continue,
78 };
79
80 if age < retention {
81 continue;
82 }
83
84 if metadata.is_dir() {
85 if fs::remove_dir_all(&path).is_ok() {
86 removed += 1;
87 } else {
88 log::warn!("failed to remove temp dir {}", path.display());
89 }
90 } else if fs::remove_file(&path).is_ok() {
91 removed += 1;
92 } else {
93 log::warn!("failed to remove temp file {}", path.display());
94 }
95 }
96
97 Ok(removed)
98}
99
100pub fn cleanup_stale_temp_dirs(base: &Path, retention: Duration) -> Result<usize> {
101 cleanup_stale_temp_entries(base, &[RALPH_TEMP_PREFIX], retention)
102}
103
104pub fn cleanup_default_temp_dirs(retention: Duration) -> Result<usize> {
105 let mut removed = 0usize;
106 removed += cleanup_stale_temp_dirs(&ralph_temp_root(), retention)?;
107 removed +=
108 cleanup_stale_temp_entries(&std::env::temp_dir(), &[LEGACY_PROMPT_PREFIX], retention)?;
109 Ok(removed)
110}
111
112pub fn create_ralph_temp_dir(label: &str) -> Result<tempfile::TempDir> {
113 let base = ralph_temp_root();
114 fs::create_dir_all(&base).with_context(|| format!("create temp dir {}", base.display()))?;
115 let prefix = format!(
116 "{prefix}{label}_",
117 prefix = RALPH_TEMP_PREFIX,
118 label = label.trim()
119 );
120 let dir = tempfile::Builder::new()
121 .prefix(&prefix)
122 .tempdir_in(&base)
123 .with_context(|| format!("create temp dir in {}", base.display()))?;
124 Ok(dir)
125}
126
127pub fn create_ralph_temp_file(label: &str) -> Result<tempfile::NamedTempFile> {
130 let base = ralph_temp_root();
131 fs::create_dir_all(&base).with_context(|| format!("create temp dir {}", base.display()))?;
132 let prefix = format!(
133 "{prefix}{label}_",
134 prefix = RALPH_TEMP_PREFIX,
135 label = label.trim()
136 );
137 tempfile::Builder::new()
138 .prefix(&prefix)
139 .suffix(".tmp")
140 .tempfile_in(&base)
141 .with_context(|| format!("create temp file in {}", base.display()))
142}