#![warn(
unused_results,
unused_qualifications,
variant_size_differences,
clippy::checked_conversions,
clippy::needless_borrow,
clippy::shadow_unrelated,
clippy::wrong_pub_self_convention
)]
#![deny(
anonymous_parameters,
bare_trait_objects,
clippy::as_conversions,
clippy::clone_on_ref_ptr,
clippy::float_cmp_const,
clippy::if_not_else,
clippy::indexing_slicing,
clippy::unwrap_used
)]
#![cfg_attr(
debug_assertions,
allow(
dead_code,
unused_imports,
unused_variables,
unreachable_code,
unused_qualifications,
)
)]
#![cfg_attr(not(debug_assertions), deny(warnings, missing_docs, clippy::dbg_macro))]
use std::convert::{TryFrom, TryInto};
use std::io::{self, Error, ErrorKind, Read, Result, Seek, SeekFrom, Write};
pub struct ShallowTees<R: Read + Seek, W: Write> {
read: R,
write: W,
cur: u64,
max: u64,
}
impl<R: Read + Seek, W: Write> std::fmt::Debug for ShallowTees<R, W> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "ShallowTees ( cur = {:?}, max = {:?} )", self.cur, self.max)
}
}
impl<R: Read + Seek, W: Write> ShallowTees<R, W> {
pub fn new(read: R, write: W) -> Self {
Self {
read,
write,
cur: 0,
max: 0,
}
}
}
impl<R: Read + Seek, W: Write> Read for ShallowTees<R, W> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
debug_assert!(
self.cur <= self.max,
"ShallowTees started at invalid state: {} > {}",
self.cur,
self.max
);
let size = self.read.read(buf)?;
if size == 0 {
return Ok(0);
}
self.cur += u64::try_from(size).expect("usize <= u64");
if self.max < self.cur {
let delta = usize::try_from(self.cur - self.max).expect("0 < delta <= size < usize");
debug_assert!(delta <= size, "self.cur <= self.max but delta > size");
self.write.write_all(
buf.get((size - delta)..size)
.expect("delta <= size <= buf.len()"),
)?;
self.max += u64::try_from(delta).expect("usize <= u64");
}
Ok(size)
}
}
impl<R: Read + Seek, W: Write> Seek for ShallowTees<R, W> {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
let dest: u64 = match pos {
SeekFrom::Start(pos) => pos,
SeekFrom::Current(pos) => {
let cur: i64 = self.cur.try_into().map_err(|_| err_ui64())?;
u64::try_from(cur + pos).map_err(|_| err_iu64())?
}
SeekFrom::End(_) => panic!("SeekFrom::End() is not supported"),
};
if dest > self.max {
let _ = self.read.seek(SeekFrom::Start(self.max))?;
self.cur = self.max;
let size = dest - self.max;
let written = io::copy(&mut (&mut self.read).take(size), &mut self.write)?;
self.cur += written;
if self.cur != dest {
return Err(Error::new(ErrorKind::UnexpectedEof, "seek behind EOF"));
}
self.max = dest;
} else {
let _ = self.read.seek(SeekFrom::Start(dest))?;
self.cur = dest;
}
Ok(self.cur)
}
}
fn err_ui64() -> Error {
Error::new(ErrorKind::Other, "offset change is greater than 2^63")
}
fn err_iu64() -> Error {
Error::new(ErrorKind::Other, "resultant offset is negative")
}
#[cfg(test)]
mod tests;