use std::io; use tracing::{debug, instrument}; use super::BlobReader; /// This implements [io::Seek] for and [io::Read] by simply skipping over some /// bytes, keeping track of the position. /// It fails whenever you try to seek backwards. pub struct DumbSeeker { r: R, pos: u64, } impl DumbSeeker { pub fn new(r: R) -> Self { DumbSeeker { r, pos: 0 } } } impl io::Read for DumbSeeker { fn read(&mut self, buf: &mut [u8]) -> io::Result { let bytes_read = self.r.read(buf)?; self.pos += bytes_read as u64; Ok(bytes_read) } } impl io::Seek for DumbSeeker { #[instrument(skip(self))] fn seek(&mut self, pos: io::SeekFrom) -> io::Result { let absolute_offset: u64 = match pos { io::SeekFrom::Start(start_offset) => { if start_offset < self.pos { return Err(io::Error::new( io::ErrorKind::Unsupported, format!("can't seek backwards ({} -> {})", self.pos, start_offset), )); } else { start_offset } } // we don't know the total size, can't support this. io::SeekFrom::End(_end_offset) => { return Err(io::Error::new( io::ErrorKind::Unsupported, "can't seek from end", )); } io::SeekFrom::Current(relative_offset) => { if relative_offset < 0 { return Err(io::Error::new( io::ErrorKind::Unsupported, "can't seek backwards relative to current position", )); } else { self.pos + relative_offset as u64 } } }; debug!(absolute_offset=?absolute_offset, "seek"); // we already know absolute_offset is larger than self.pos debug_assert!( absolute_offset >= self.pos, "absolute_offset {} is larger than self.pos {}", absolute_offset, self.pos ); // calculate bytes to skip let bytes_to_skip: u64 = absolute_offset - self.pos; // discard these bytes. We can't use take() as it requires ownership of // self.r, but we only have &mut self. let mut buf = [0; 1024]; let mut bytes_skipped: u64 = 0; while bytes_skipped < bytes_to_skip { let len = std::cmp::min(bytes_to_skip - bytes_skipped, buf.len() as u64); match self.r.read(&mut buf[..len as usize]) { Ok(0) => break, Ok(n) => bytes_skipped += n as u64, Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => {} Err(e) => return Err(e), } } // This will fail when seeking past the end of self.r if bytes_to_skip != bytes_skipped { return Err(std::io::Error::new( std::io::ErrorKind::UnexpectedEof, format!( "tried to skip {} bytes, but only was able to skip {} until reaching EOF", bytes_to_skip, bytes_skipped ), )); } self.pos = absolute_offset; // return the new position from the start of the stream Ok(absolute_offset) } } /// A Cursor> can be used as a BlobReader. impl BlobReader for DumbSeeker {}