virtual_file_transfer/
source_reader.rs1use std::io::{self, Read, Seek, SeekFrom};
2
3use crate::source_reader::ReaderKind::{Seekable, Streaming};
4
5pub struct SourceReader {
14 inner: ReaderKind,
15}
16
17pub trait ReadSeek: Read + Seek {}
20
21impl<T> ReadSeek for T where T: Read + Seek {}
22
23enum ReaderKind {
24 Streaming(Box<dyn Read + Send>),
25 Seekable(Box<dyn ReadSeek + Send>),
26}
27
28#[derive(Debug)]
29pub(crate) enum SourceReaderError {
30 NotSeekable,
31 Io,
32}
33
34impl SourceReader {
35 pub fn streaming<R>(reader: R) -> Self
41 where
42 R: Read + Send + 'static,
43 {
44 Self {
45 inner: Streaming(Box::new(reader)),
46 }
47 }
48
49 pub fn seekable<R>(reader: R) -> Self
55 where
56 R: Read + Seek + Send + 'static,
57 {
58 Self {
59 inner: Seekable(Box::new(reader)),
60 }
61 }
62
63 #[cfg(feature = "spool")]
77 pub fn spooled<R>(reader: R) -> io::Result<Self>
78 where
79 R: Read + Send + 'static,
80 {
81 Ok(Self::seekable(spool_to_file(reader)?))
82 }
83
84 pub(crate) fn seek(&mut self, position: SeekFrom) -> Result<u64, SourceReaderError> {
85 match &mut self.inner {
86 ReaderKind::Seekable(reader) => {
87 reader.seek(position).map_err(|_| SourceReaderError::Io)
88 }
89
90 ReaderKind::Streaming(_) => Err(SourceReaderError::NotSeekable),
91 }
92 }
93
94 pub(crate) fn try_size(&mut self) -> io::Result<Option<u64>> {
95 match &mut self.inner {
96 ReaderKind::Streaming(_) => Ok(None),
97
98 ReaderKind::Seekable(reader) => {
99 let current = reader.stream_position()?;
100 let size = reader.seek(SeekFrom::End(0))?;
101 reader.seek(SeekFrom::Start(current))?;
102
103 Ok(Some(size))
104 }
105 }
106 }
107}
108
109impl Read for SourceReader {
110 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
111 match &mut self.inner {
112 ReaderKind::Streaming(reader) => reader.read(buffer),
113 ReaderKind::Seekable(reader) => reader.read(buffer),
114 }
115 }
116}
117
118#[cfg(feature = "spool")]
119fn spool_to_file(mut reader: impl Read) -> io::Result<std::fs::File> {
120 let mut file = tempfile::tempfile()?;
121
122 io::copy(&mut reader, &mut file)?;
123 file.seek(SeekFrom::Start(0))?;
124
125 Ok(file)
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn streaming_reader_is_not_seekable() {
134 let mut reader = SourceReader::streaming(std::io::Cursor::new(b"hello".to_vec()));
135
136 assert!(matches!(
137 reader.seek(SeekFrom::Start(0)),
138 Err(SourceReaderError::NotSeekable)
139 ));
140 }
141
142 #[test]
143 fn streaming_reader_cannot_derive_size() {
144 let mut reader = SourceReader::streaming(std::io::Cursor::new(b"hello".to_vec()));
145
146 assert_eq!(reader.try_size().unwrap(), None);
147 }
148
149 #[test]
150 fn seekable_reader_can_seek() {
151 let mut reader = SourceReader::seekable(std::io::Cursor::new(b"hello".to_vec()));
152
153 assert_eq!(reader.seek(SeekFrom::Start(2)).unwrap(), 2);
154
155 let mut buf = [0u8; 2];
156 reader.read_exact(&mut buf).unwrap();
157
158 assert_eq!(&buf, b"ll");
159 }
160
161 #[test]
162 fn seekable_reader_can_derive_size() {
163 let mut reader = SourceReader::seekable(std::io::Cursor::new(b"hello".to_vec()));
164
165 assert_eq!(reader.try_size().unwrap(), Some(5));
166 }
167
168 #[test]
169 fn try_size_restores_position() {
170 let mut reader = SourceReader::seekable(std::io::Cursor::new(b"hello".to_vec()));
171
172 reader.seek(SeekFrom::Start(2)).unwrap();
173
174 assert_eq!(reader.try_size().unwrap(), Some(5));
175 assert_eq!(reader.seek(SeekFrom::Current(0)).unwrap(), 2);
176 }
177
178 #[cfg(feature = "spool")]
179 #[test]
180 fn spooled_reader_preserves_data_and_supports_seek() {
181 let data: Vec<u8> = (0..256 * 1024).map(|i| (i % 251) as u8).collect();
182
183 let mut reader = SourceReader::spooled(io::Cursor::new(data.clone())).unwrap();
184
185 assert_eq!(reader.try_size().unwrap(), Some(data.len() as u64));
187
188 let mut actual = Vec::new();
190 reader.read_to_end(&mut actual).unwrap();
191 assert_eq!(actual, data);
192
193 assert_eq!(reader.seek(SeekFrom::Start(42)).unwrap(), 42);
195
196 assert_eq!(reader.try_size().unwrap(), Some(data.len() as u64));
198 assert_eq!(reader.seek(SeekFrom::Current(0)).unwrap(), 42);
199
200 let mut buf = [0u8; 4];
201 reader.read_exact(&mut buf).unwrap();
202 assert_eq!(&buf, &data[42..46]);
203 }
204
205 #[cfg(feature = "spool")]
206 #[test]
207 fn spooled_reader_accepts_empty_source() {
208 let mut reader = SourceReader::spooled(io::empty()).unwrap();
209
210 assert_eq!(reader.try_size().unwrap(), Some(0));
211
212 let mut buf = [0u8; 1];
213 assert_eq!(reader.read(&mut buf).unwrap(), 0);
214 }
215
216 #[cfg(feature = "spool")]
217 #[test]
218 fn spooled_reader_propagates_source_error() {
219 struct FailingReader;
220
221 impl Read for FailingReader {
222 fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
223 Err(io::Error::other("source read failed"))
224 }
225 }
226
227 let result = SourceReader::spooled(FailingReader);
228
229 assert!(matches!(
230 result,
231 Err(ref err) if err.kind() == io::ErrorKind::Other
232 ));
233 }
234
235 #[cfg(feature = "spool")]
236 #[test]
237 fn spool_creates_file_with_correct_contents() {
238 let data = b"hello spool";
239
240 let mut file = spool_to_file(io::Cursor::new(data)).unwrap();
241
242 assert_eq!(file.metadata().unwrap().len(), data.len() as u64);
243
244 let mut actual = Vec::new();
245 file.read_to_end(&mut actual).unwrap();
246
247 assert_eq!(actual, data);
248
249 drop(file);
250 }
251
252 #[cfg(feature = "spool")]
253 #[test]
254 fn spooled_reader_supports_unknown_size_non_seekable_source() {
255 const SIZE: u64 = 1024 * 1024 + 7;
256
257 let mut source = SourceReader::streaming(io::repeat(0x5A).take(SIZE));
258
259 assert_eq!(source.try_size().unwrap(), None);
260 assert!(matches!(
261 source.seek(SeekFrom::Start(0)),
262 Err(SourceReaderError::NotSeekable)
263 ));
264
265 let mut reader = SourceReader::spooled(source).unwrap();
266
267 assert_eq!(reader.try_size().unwrap(), Some(SIZE));
268
269 assert_eq!(reader.seek(SeekFrom::End(-1)).unwrap(), SIZE - 1);
270
271 let mut buf = [0u8; 1];
272 reader.read_exact(&mut buf).unwrap();
273 assert_eq!(buf[0], 0x5A);
274
275 assert_eq!(reader.read(&mut buf).unwrap(), 0);
276
277 assert_eq!(reader.seek(SeekFrom::Start(0)).unwrap(), 0);
278 reader.read_exact(&mut buf).unwrap();
279 assert_eq!(buf[0], 0x5A);
280 }
281}