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}