Skip to main content

qubit_fs/temp/
managed_temp_dir.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Managed temporary directory implementation.
11
12use std::sync::Arc;
13
14use log::warn;
15
16use crate::{
17    CopyConflictPolicy,
18    CopyOptions,
19    DeleteOptions,
20    FileSystem,
21    FsErrorKind,
22    FsPath,
23    FsResult,
24    PersistOptions,
25    RenameOptions,
26    TempDir,
27    TempResource,
28};
29
30/// Default temporary directory backed by an [`Arc`] filesystem and path.
31#[derive(Debug)]
32pub struct ManagedTempDir {
33    /// Filesystem that owns the temporary path.
34    fs: Arc<dyn FileSystem>,
35    /// Temporary path.
36    path: FsPath,
37    /// Whether drop should attempt best-effort cleanup.
38    cleanup_on_drop: bool,
39}
40
41impl ManagedTempDir {
42    /// Creates a managed temporary directory handle.
43    ///
44    /// # Parameters
45    /// - `fs`: Filesystem that owns the temporary path.
46    /// - `path`: Temporary directory path.
47    ///
48    /// # Returns
49    /// Managed temporary directory handle.
50    #[inline]
51    #[must_use]
52    pub fn new(fs: Arc<dyn FileSystem>, path: FsPath) -> Self {
53        Self {
54            fs,
55            path,
56            cleanup_on_drop: true,
57        }
58    }
59
60    /// Disables drop cleanup and returns the temporary path.
61    ///
62    /// # Returns
63    /// Retained temporary path.
64    #[inline]
65    fn detach(&mut self) -> FsPath {
66        self.cleanup_on_drop = false;
67        self.path.clone()
68    }
69}
70
71impl TempResource for ManagedTempDir {
72    #[inline]
73    fn fs(&self) -> Arc<dyn FileSystem> {
74        self.fs.clone()
75    }
76
77    #[inline]
78    fn path(&self) -> &FsPath {
79        &self.path
80    }
81
82    fn cleanup(mut self: Box<Self>) -> FsResult<()> {
83        let result = self.fs.delete(
84            &self.path,
85            &DeleteOptions {
86                recursive: true,
87                missing_ok: true,
88                if_match: None,
89            },
90        );
91        if result.is_ok() {
92            self.cleanup_on_drop = false;
93        }
94        result
95    }
96
97    fn keep(mut self: Box<Self>) -> FsResult<FsPath> {
98        Ok(self.detach())
99    }
100}
101
102impl TempDir for ManagedTempDir {
103    fn persist(mut self: Box<Self>, target: &FsPath, options: &PersistOptions) -> FsResult<()> {
104        let rename_options = RenameOptions {
105            overwrite: options.overwrite,
106            atomic: options.atomic,
107        };
108        match self.fs.rename(&self.path, target, &rename_options) {
109            Ok(()) => {
110                self.cleanup_on_drop = false;
111                Ok(())
112            }
113            Err(error) if error.kind() == FsErrorKind::UnsupportedOperation && options.allow_copy_delete => {
114                let mut copy_options = CopyOptions::tree();
115                copy_options.conflict = if options.overwrite {
116                    CopyConflictPolicy::Overwrite
117                } else {
118                    CopyConflictPolicy::Fail
119                };
120                copy_options.preserve_metadata = options.preserve_metadata;
121                self.fs.copy(&self.path, target, &copy_options)?;
122                self.fs.delete(
123                    &self.path,
124                    &DeleteOptions {
125                        recursive: true,
126                        missing_ok: true,
127                        if_match: None,
128                    },
129                )?;
130                self.cleanup_on_drop = false;
131                Ok(())
132            }
133            Err(error) => Err(error),
134        }
135    }
136}
137
138impl Drop for ManagedTempDir {
139    fn drop(&mut self) {
140        if self.cleanup_on_drop {
141            let result = self.fs.delete(
142                &self.path,
143                &DeleteOptions {
144                    recursive: true,
145                    missing_ok: true,
146                    if_match: None,
147                },
148            );
149            if let Err(error) = result {
150                warn!("failed to cleanup temporary directory '{}': {error}", self.path);
151            }
152        }
153    }
154}