1use crate::file::tempfile;
2use std::fs::File;
3use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};
4
5#[derive(Debug)]
7pub enum SpooledData {
8 InMemory(Cursor<Vec<u8>>),
9 OnDisk(File),
10}
11
12#[derive(Debug)]
17pub struct SpooledTempFile {
18 max_size: usize,
19 inner: SpooledData,
20}
21
22#[inline]
54pub fn spooled_tempfile(max_size: usize) -> SpooledTempFile {
55 SpooledTempFile::new(max_size)
56}
57
58impl SpooledTempFile {
59 #[must_use]
60 pub fn new(max_size: usize) -> SpooledTempFile {
61 SpooledTempFile {
62 max_size,
63 inner: SpooledData::InMemory(Cursor::new(Vec::new())),
64 }
65 }
66
67 #[must_use]
69 pub fn is_rolled(&self) -> bool {
70 match self.inner {
71 SpooledData::InMemory(_) => false,
72 SpooledData::OnDisk(_) => true,
73 }
74 }
75
76 pub fn roll(&mut self) -> io::Result<()> {
79 if !self.is_rolled() {
80 let mut file = tempfile()?;
81 if let SpooledData::InMemory(cursor) = &mut self.inner {
82 file.write_all(cursor.get_ref())?;
83 file.seek(SeekFrom::Start(cursor.position()))?;
84 }
85 self.inner = SpooledData::OnDisk(file);
86 }
87 Ok(())
88 }
89
90 pub fn set_len(&mut self, size: u64) -> Result<(), io::Error> {
91 if size > self.max_size as u64 {
92 self.roll()?; }
94 match &mut self.inner {
95 SpooledData::InMemory(cursor) => {
96 cursor.get_mut().resize(size as usize, 0);
97 Ok(())
98 }
99 SpooledData::OnDisk(file) => file.set_len(size),
100 }
101 }
102
103 #[must_use]
105 pub fn into_inner(self) -> SpooledData {
106 self.inner
107 }
108}
109
110impl Read for SpooledTempFile {
111 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
112 match &mut self.inner {
113 SpooledData::InMemory(cursor) => cursor.read(buf),
114 SpooledData::OnDisk(file) => file.read(buf),
115 }
116 }
117
118 fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
119 match &mut self.inner {
120 SpooledData::InMemory(cursor) => cursor.read_vectored(bufs),
121 SpooledData::OnDisk(file) => file.read_vectored(bufs),
122 }
123 }
124
125 fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
126 match &mut self.inner {
127 SpooledData::InMemory(cursor) => cursor.read_to_end(buf),
128 SpooledData::OnDisk(file) => file.read_to_end(buf),
129 }
130 }
131
132 fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
133 match &mut self.inner {
134 SpooledData::InMemory(cursor) => cursor.read_to_string(buf),
135 SpooledData::OnDisk(file) => file.read_to_string(buf),
136 }
137 }
138
139 fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
140 match &mut self.inner {
141 SpooledData::InMemory(cursor) => cursor.read_exact(buf),
142 SpooledData::OnDisk(file) => file.read_exact(buf),
143 }
144 }
145}
146
147impl Write for SpooledTempFile {
148 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
149 if matches! {
151 &self.inner, SpooledData::InMemory(cursor)
152 if cursor.position().saturating_add(buf.len() as u64) > self.max_size as u64
153 } {
154 self.roll()?;
155 }
156
157 match &mut self.inner {
159 SpooledData::InMemory(cursor) => cursor.write(buf),
160 SpooledData::OnDisk(file) => file.write(buf),
161 }
162 }
163
164 fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
165 if matches! {
166 &self.inner, SpooledData::InMemory(cursor)
167 if bufs
169 .iter()
170 .fold(cursor.position(), |a, b| a.saturating_add(b.len() as u64))
171 > self.max_size as u64
172 } {
173 self.roll()?;
174 }
175 match &mut self.inner {
176 SpooledData::InMemory(cursor) => cursor.write_vectored(bufs),
177 SpooledData::OnDisk(file) => file.write_vectored(bufs),
178 }
179 }
180
181 #[inline]
182 fn flush(&mut self) -> io::Result<()> {
183 match &mut self.inner {
184 SpooledData::InMemory(cursor) => cursor.flush(),
185 SpooledData::OnDisk(file) => file.flush(),
186 }
187 }
188}
189
190impl Seek for SpooledTempFile {
191 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
192 match &mut self.inner {
193 SpooledData::InMemory(cursor) => cursor.seek(pos),
194 SpooledData::OnDisk(file) => file.seek(pos),
195 }
196 }
197}