/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 | | } |