agpm_cli/utils/fs/temp.rs
1//! Temporary directory management with RAII cleanup.
2//!
3//! This module provides a `TempDir` struct that automatically cleans up
4//! temporary directories when dropped.
5
6use crate::utils::fs::dirs::{ensure_dir, remove_dir_all};
7use anyhow::Result;
8use std::path::{Path, PathBuf};
9
10/// A temporary directory that automatically cleans up when dropped.
11///
12/// This struct provides RAII (Resource Acquisition Is Initialization) semantics
13/// for temporary directories. The directory is created when the struct is created
14/// and automatically removed when the struct is dropped, even if the program panics.
15///
16/// # Examples
17///
18/// ```rust,no_run
19/// use agpm_cli::utils::fs::TempDir;
20///
21/// # fn example() -> anyhow::Result<()> {
22/// {
23/// let temp = TempDir::new("test")?;
24/// let temp_path = temp.path();
25///
26/// // Use the temporary directory
27/// std::fs::write(temp_path.join("file.txt"), "temporary data")?;
28///
29/// // Directory exists here
30/// assert!(temp_path.exists());
31/// } // TempDir is dropped here, directory is automatically cleaned up
32/// # Ok(())
33/// # }
34/// ```
35///
36/// # Thread Safety
37///
38/// Each `TempDir` instance creates a unique directory using UUID generation,
39/// making it safe to use across multiple threads without naming conflicts.
40///
41/// # Cleanup Behavior
42///
43/// - Directory is removed recursively when dropped
44/// - Cleanup happens even if the program panics
45/// - If cleanup fails (rare), the error is silently ignored
46/// - Uses the system temporary directory as the parent
47///
48/// # Use Cases
49///
50/// - Unit testing with temporary files
51/// - Staging areas for atomic operations
52/// - Scratch space for temporary processing
53pub struct TempDir {
54 path: PathBuf,
55}
56
57impl TempDir {
58 /// Creates a new temporary directory with the given prefix.
59 ///
60 /// The directory is created immediately and will have a name like
61 /// `agpm_{prefix}_{uuid}` in the system temporary directory.
62 ///
63 /// # Arguments
64 ///
65 /// * `prefix` - A prefix for the directory name (for identification)
66 ///
67 /// # Returns
68 ///
69 /// A new `TempDir` instance, or an error if directory creation fails
70 ///
71 /// # Examples
72 ///
73 /// ```rust,no_run
74 /// use agpm_cli::utils::fs::TempDir;
75 ///
76 /// # fn example() -> anyhow::Result<()> {
77 /// let temp = TempDir::new("cache")?;
78 /// println!("Temporary directory: {}", temp.path().display());
79 /// # Ok(())
80 /// # }
81 /// ```
82 pub fn new(prefix: &str) -> Result<Self> {
83 let temp_dir = std::env::temp_dir();
84 let unique_name = format!("agpm_{}_{}", prefix, uuid::Uuid::new_v4());
85 let path = temp_dir.join(unique_name);
86
87 ensure_dir(&path)?;
88
89 Ok(Self {
90 path,
91 })
92 }
93
94 /// Returns the path to the temporary directory.
95 ///
96 /// The directory is guaranteed to exist as long as this `TempDir` instance exists.
97 ///
98 /// # Returns
99 ///
100 /// A reference to the temporary directory path
101 #[must_use]
102 pub fn path(&self) -> &Path {
103 &self.path
104 }
105}
106
107impl Drop for TempDir {
108 fn drop(&mut self) {
109 let _ = remove_dir_all(&self.path);
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_temp_dir() {
119 let temp_dir = TempDir::new("test").unwrap();
120 let path = temp_dir.path().to_path_buf();
121
122 assert!(path.exists());
123 assert!(path.is_dir());
124
125 // Write a file to verify it's a real directory
126 std::fs::write(path.join("test.txt"), "test").unwrap();
127 assert!(path.join("test.txt").exists());
128
129 drop(temp_dir);
130 // Directory should be cleaned up
131 assert!(!path.exists());
132 }
133
134 #[test]
135 fn test_temp_dir_custom_prefix() {
136 let temp1 = TempDir::new("prefix1").unwrap();
137 let temp2 = TempDir::new("prefix2").unwrap();
138
139 assert!(temp1.path().to_string_lossy().contains("prefix1"));
140 assert!(temp2.path().to_string_lossy().contains("prefix2"));
141
142 let path1 = temp1.path().to_path_buf();
143 let path2 = temp2.path().to_path_buf();
144
145 assert_ne!(path1, path2);
146 assert!(path1.exists());
147 assert!(path2.exists());
148 }
149}