Coverage Report

Created: 2025-02-21 07:11

/rust/registry/src/index.crates.io-6f17d22bba15001f/tempfile-3.16.0/src/spooled.rs
Line
Count
Source (jump to first uncovered line)
1
use crate::file::tempfile;
2
use std::fs::File;
3
use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};
4
5
/// A wrapper for the two states of a `SpooledTempFile`.
6
#[derive(Debug)]
7
pub enum SpooledData {
8
    InMemory(Cursor<Vec<u8>>),
9
    OnDisk(File),
10
}
11
12
/// An object that behaves like a regular temporary file, but keeps data in
13
/// memory until it reaches a configured size, at which point the data is
14
/// written to a temporary file on disk, and further operations use the file
15
/// on disk.
16
#[derive(Debug)]
17
pub struct SpooledTempFile {
18
    max_size: usize,
19
    inner: SpooledData,
20
}
21
22
/// Create a new spooled temporary file.
23
///
24
/// # Security
25
///
26
/// This variant is secure/reliable in the presence of a pathological temporary
27
/// file cleaner.
28
///
29
/// # Resource Leaking
30
///
31
/// The temporary file will be automatically removed by the OS when the last
32
/// handle to it is closed. This doesn't rely on Rust destructors being run, so
33
/// will (almost) never fail to clean up the temporary file.
34
///
35
/// # Examples
36
///
37
/// ```
38
/// use tempfile::spooled_tempfile;
39
/// use std::io::Write;
40
///
41
/// let mut file = spooled_tempfile(15);
42
///
43
/// writeln!(file, "short line")?;
44
/// assert!(!file.is_rolled());
45
///
46
/// // as a result of this write call, the size of the data will exceed
47
/// // `max_size` (15), so it will be written to a temporary file on disk,
48
/// // and the in-memory buffer will be dropped
49
/// writeln!(file, "marvin gardens")?;
50
/// assert!(file.is_rolled());
51
/// # Ok::<(), std::io::Error>(())
52
/// ```
53
#[inline]
54
0
pub fn spooled_tempfile(max_size: usize) -> SpooledTempFile {
55
0
    SpooledTempFile::new(max_size)
56
0
}
57
58
impl SpooledTempFile {
59
    #[must_use]
60
0
    pub fn new(max_size: usize) -> SpooledTempFile {
61
0
        SpooledTempFile {
62
0
            max_size,
63
0
            inner: SpooledData::InMemory(Cursor::new(Vec::new())),
64
0
        }
65
0
    }
66
67
    /// Returns true if the file has been rolled over to disk.
68
    #[must_use]
69
0
    pub fn is_rolled(&self) -> bool {
70
0
        match self.inner {
71
0
            SpooledData::InMemory(_) => false,
72
0
            SpooledData::OnDisk(_) => true,
73
        }
74
0
    }
75
76
    /// Rolls over to a file on disk, regardless of current size. Does nothing
77
    /// if already rolled over.
78
0
    pub fn roll(&mut self) -> io::Result<()> {
79
0
        if !self.is_rolled() {
80
0
            let mut file = tempfile()?;
81
0
            if let SpooledData::InMemory(cursor) = &mut self.inner {
82
0
                file.write_all(cursor.get_ref())?;
83
0
                file.seek(SeekFrom::Start(cursor.position()))?;
84
0
            }
85
0
            self.inner = SpooledData::OnDisk(file);
86
0
        }
87
0
        Ok(())
88
0
    }
89
90
0
    pub fn set_len(&mut self, size: u64) -> Result<(), io::Error> {
91
0
        if size > self.max_size as u64 {
92
0
            self.roll()?; // does nothing if already rolled over
93
0
        }
94
0
        match &mut self.inner {
95
0
            SpooledData::InMemory(cursor) => {
96
0
                cursor.get_mut().resize(size as usize, 0);
97
0
                Ok(())
98
            }
99
0
            SpooledData::OnDisk(file) => file.set_len(size),
100
        }
101
0
    }
102
103
    /// Consumes and returns the inner `SpooledData` type.
104
    #[must_use]
105
0
    pub fn into_inner(self) -> SpooledData {
106
0
        self.inner
107
0
    }
108
}
109
110
impl Read for SpooledTempFile {
111
0
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
112
0
        match &mut self.inner {
113
0
            SpooledData::InMemory(cursor) => cursor.read(buf),
114
0
            SpooledData::OnDisk(file) => file.read(buf),
115
        }
116
0
    }
117
118
0
    fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
119
0
        match &mut self.inner {
120
0
            SpooledData::InMemory(cursor) => cursor.read_vectored(bufs),
121
0
            SpooledData::OnDisk(file) => file.read_vectored(bufs),
122
        }
123
0
    }
124
125
0
    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
126
0
        match &mut self.inner {
127
0
            SpooledData::InMemory(cursor) => cursor.read_to_end(buf),
128
0
            SpooledData::OnDisk(file) => file.read_to_end(buf),
129
        }
130
0
    }
131
132
0
    fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
133
0
        match &mut self.inner {
134
0
            SpooledData::InMemory(cursor) => cursor.read_to_string(buf),
135
0
            SpooledData::OnDisk(file) => file.read_to_string(buf),
136
        }
137
0
    }
138
139
0
    fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
140
0
        match &mut self.inner {
141
0
            SpooledData::InMemory(cursor) => cursor.read_exact(buf),
142
0
            SpooledData::OnDisk(file) => file.read_exact(buf),
143
        }
144
0
    }
145
}
146
147
impl Write for SpooledTempFile {
148
0
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
149
        // roll over to file if necessary
150
0
        if matches! {
151
0
            &self.inner, SpooledData::InMemory(cursor)
152
0
            if cursor.position().saturating_add(buf.len() as u64) > self.max_size as u64
153
        } {
154
0
            self.roll()?;
155
0
        }
156
157
        // write the bytes
158
0
        match &mut self.inner {
159
0
            SpooledData::InMemory(cursor) => cursor.write(buf),
160
0
            SpooledData::OnDisk(file) => file.write(buf),
161
        }
162
0
    }
163
164
0
    fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
165
0
        if matches! {
166
0
            &self.inner, SpooledData::InMemory(cursor)
167
0
            // Borrowed from the rust standard library.
168
0
            if bufs
169
0
                .iter()
170
0
                .fold(cursor.position(), |a, b| a.saturating_add(b.len() as u64))
171
0
                > self.max_size as u64
172
        } {
173
0
            self.roll()?;
174
0
        }
175
0
        match &mut self.inner {
176
0
            SpooledData::InMemory(cursor) => cursor.write_vectored(bufs),
177
0
            SpooledData::OnDisk(file) => file.write_vectored(bufs),
178
        }
179
0
    }
180
181
    #[inline]
182
0
    fn flush(&mut self) -> io::Result<()> {
183
0
        match &mut self.inner {
184
0
            SpooledData::InMemory(cursor) => cursor.flush(),
185
0
            SpooledData::OnDisk(file) => file.flush(),
186
        }
187
0
    }
188
}
189
190
impl Seek for SpooledTempFile {
191
0
    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
192
0
        match &mut self.inner {
193
0
            SpooledData::InMemory(cursor) => cursor.seek(pos),
194
0
            SpooledData::OnDisk(file) => file.seek(pos),
195
        }
196
0
    }
197
}