1use crate::file::tempfile;
2use crate::tempfile_in;
3use std::fs::File;
4use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};
5use std::path::{Path, PathBuf};
6
7#[derive(Debug)]
12pub enum SpooledData {
13 InMemory(Cursor<Vec<u8>>),
14 OnDisk(File),
15}
16
17#[derive(Debug)]
22pub struct SpooledTempFile {
23 max_size: usize,
24 dir: Option<PathBuf>,
25 inner: SpooledData,
26}
27
28#[inline]
67pub fn spooled_tempfile(max_size: usize) -> SpooledTempFile {
68 SpooledTempFile::new(max_size)
69}
70
71#[inline]
79pub fn spooled_tempfile_in<P: AsRef<Path>>(max_size: usize, dir: P) -> SpooledTempFile {
80 SpooledTempFile::new_in(max_size, dir)
81}
82
83fn cursor_to_tempfile(cursor: &Cursor<Vec<u8>>, p: &Option<PathBuf>) -> io::Result<File> {
85 let mut file = match p {
86 Some(p) => tempfile_in(p)?,
87 None => tempfile()?,
88 };
89 file.write_all(cursor.get_ref())?;
90 file.seek(SeekFrom::Start(cursor.position()))?;
91 Ok(file)
92}
93
94impl SpooledTempFile {
95 #[must_use]
97 pub fn new(max_size: usize) -> SpooledTempFile {
98 SpooledTempFile {
99 max_size,
100 dir: None,
101 inner: SpooledData::InMemory(Cursor::new(Vec::new())),
102 }
103 }
104
105 #[must_use]
107 pub fn new_in<P: AsRef<Path>>(max_size: usize, dir: P) -> SpooledTempFile {
108 SpooledTempFile {
109 max_size,
110 dir: Some(dir.as_ref().to_owned()),
111 inner: SpooledData::InMemory(Cursor::new(Vec::new())),
112 }
113 }
114
115 #[must_use]
117 pub fn is_rolled(&self) -> bool {
118 match self.inner {
119 SpooledData::InMemory(_) => false,
120 SpooledData::OnDisk(_) => true,
121 }
122 }
123
124 pub fn roll(&mut self) -> io::Result<()> {
127 if let SpooledData::InMemory(cursor) = &mut self.inner {
128 self.inner = SpooledData::OnDisk(cursor_to_tempfile(cursor, &self.dir)?);
129 }
130 Ok(())
131 }
132
133 pub fn set_len(&mut self, size: u64) -> Result<(), io::Error> {
135 if size > self.max_size as u64 {
136 self.roll()?; }
138 match &mut self.inner {
139 SpooledData::InMemory(cursor) => {
140 cursor.get_mut().resize(size as usize, 0);
141 Ok(())
142 }
143 SpooledData::OnDisk(file) => file.set_len(size),
144 }
145 }
146
147 #[must_use]
149 pub fn into_inner(self) -> SpooledData {
150 self.inner
151 }
152
153 pub fn into_file(self) -> io::Result<File> {
155 match self.inner {
156 SpooledData::InMemory(cursor) => cursor_to_tempfile(&cursor, &self.dir),
157 SpooledData::OnDisk(file) => Ok(file),
158 }
159 }
160}
161
162impl Read for SpooledTempFile {
163 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
164 match &mut self.inner {
165 SpooledData::InMemory(cursor) => cursor.read(buf),
166 SpooledData::OnDisk(file) => file.read(buf),
167 }
168 }
169
170 fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
171 match &mut self.inner {
172 SpooledData::InMemory(cursor) => cursor.read_vectored(bufs),
173 SpooledData::OnDisk(file) => file.read_vectored(bufs),
174 }
175 }
176
177 fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
178 match &mut self.inner {
179 SpooledData::InMemory(cursor) => cursor.read_to_end(buf),
180 SpooledData::OnDisk(file) => file.read_to_end(buf),
181 }
182 }
183
184 fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
185 match &mut self.inner {
186 SpooledData::InMemory(cursor) => cursor.read_to_string(buf),
187 SpooledData::OnDisk(file) => file.read_to_string(buf),
188 }
189 }
190
191 fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
192 match &mut self.inner {
193 SpooledData::InMemory(cursor) => cursor.read_exact(buf),
194 SpooledData::OnDisk(file) => file.read_exact(buf),
195 }
196 }
197}
198
199impl Write for SpooledTempFile {
200 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
201 if matches! {
203 &self.inner, SpooledData::InMemory(cursor)
204 if cursor.position().saturating_add(buf.len() as u64) > self.max_size as u64
205 } {
206 self.roll()?;
207 }
208
209 match &mut self.inner {
211 SpooledData::InMemory(cursor) => cursor.write(buf),
212 SpooledData::OnDisk(file) => file.write(buf),
213 }
214 }
215
216 fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
217 if matches! {
218 &self.inner, SpooledData::InMemory(cursor)
219 if bufs
221 .iter()
222 .fold(cursor.position(), |a, b| a.saturating_add(b.len() as u64))
223 > self.max_size as u64
224 } {
225 self.roll()?;
226 }
227 match &mut self.inner {
228 SpooledData::InMemory(cursor) => cursor.write_vectored(bufs),
229 SpooledData::OnDisk(file) => file.write_vectored(bufs),
230 }
231 }
232
233 #[inline]
234 fn flush(&mut self) -> io::Result<()> {
235 match &mut self.inner {
236 SpooledData::InMemory(cursor) => cursor.flush(),
237 SpooledData::OnDisk(file) => file.flush(),
238 }
239 }
240}
241
242impl Seek for SpooledTempFile {
243 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
244 match &mut self.inner {
245 SpooledData::InMemory(cursor) => cursor.seek(pos),
246 SpooledData::OnDisk(file) => file.seek(pos),
247 }
248 }
249}