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}