shallow_tees/
lib.rs

1#![warn(
2    unused_results,
3    unused_qualifications,
4    variant_size_differences,
5    clippy::checked_conversions,
6    clippy::needless_borrow,
7    clippy::shadow_unrelated,
8    clippy::wrong_pub_self_convention
9)]
10#![deny(
11    anonymous_parameters,
12    bare_trait_objects,
13    clippy::as_conversions,
14    clippy::clone_on_ref_ptr,
15    clippy::float_cmp_const,
16    clippy::if_not_else,
17    clippy::indexing_slicing,
18    clippy::unwrap_used
19)]
20#![cfg_attr(
21    debug_assertions,
22    allow(
23        dead_code,
24        unused_imports,
25        unused_variables,
26        unreachable_code,
27        unused_qualifications,
28    )
29)]
30#![cfg_attr(not(debug_assertions), deny(warnings, missing_docs, clippy::dbg_macro))]
31
32//! A `tee` implementation with Seek support through counting the max read.
33
34use std::convert::{TryFrom, TryInto};
35use std::io::{self, Error, ErrorKind, Read, Result, Seek, SeekFrom, Write};
36
37/// A tee-reader implementation that implements Seek only on the shallow level,
38/// i.e. the Write still receives all the bytes skipped in the seek.
39pub struct ShallowTees<R: Read + Seek, W: Write> {
40    read: R,
41    write: W,
42    /// current offset of R
43    cur: u64,
44    /// maximum offset read from R
45    max: u64,
46}
47
48impl<R: Read + Seek, W: Write> std::fmt::Debug for ShallowTees<R, W> {
49    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
50        write!(f, "ShallowTees ( cur = {:?}, max = {:?} )", self.cur, self.max)
51    }
52}
53
54impl<R: Read + Seek, W: Write> ShallowTees<R, W> {
55    /// Creates a ShallowTees
56    pub fn new(read: R, write: W) -> Self {
57        Self {
58            read,
59            write,
60            cur: 0,
61            max: 0,
62        }
63    }
64}
65
66impl<R: Read + Seek, W: Write> Read for ShallowTees<R, W> {
67    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
68        debug_assert!(
69            self.cur <= self.max,
70            "ShallowTees started at invalid state: {} > {}",
71            self.cur,
72            self.max
73        );
74        let size = self.read.read(buf)?;
75        if size == 0 {
76            return Ok(0);
77        }
78        self.cur += u64::try_from(size).expect("usize <= u64");
79        if self.max < self.cur {
80            let delta = usize::try_from(self.cur - self.max).expect("0 < delta <= size < usize");
81            debug_assert!(delta <= size, "self.cur <= self.max but delta > size");
82            self.write.write_all(
83                buf.get((size - delta)..size)
84                    .expect("delta <= size <= buf.len()"),
85            )?;
86            self.max += u64::try_from(delta).expect("usize <= u64");
87        }
88        Ok(size)
89    }
90}
91
92impl<R: Read + Seek, W: Write> Seek for ShallowTees<R, W> {
93    fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
94        let dest: u64 = match pos {
95            SeekFrom::Start(pos) => pos,
96            SeekFrom::Current(pos) => {
97                let cur: i64 = self.cur.try_into().map_err(|_| err_ui64())?;
98                u64::try_from(cur + pos).map_err(|_| err_iu64())?
99            }
100            SeekFrom::End(_) => panic!("SeekFrom::End() is not supported"),
101        };
102
103        if dest > self.max {
104            let _ = self.read.seek(SeekFrom::Start(self.max))?;
105            self.cur = self.max;
106            let size = dest - self.max;
107            let written = io::copy(&mut (&mut self.read).take(size), &mut self.write)?;
108            self.cur += written;
109            if self.cur != dest {
110                return Err(Error::new(ErrorKind::UnexpectedEof, "seek behind EOF"));
111            }
112            self.max = dest;
113        } else {
114            let _ = self.read.seek(SeekFrom::Start(dest))?;
115            self.cur = dest;
116        }
117
118        Ok(self.cur)
119    }
120}
121
122fn err_ui64() -> Error {
123    Error::new(ErrorKind::Other, "offset change is greater than 2^63")
124}
125
126fn err_iu64() -> Error {
127    Error::new(ErrorKind::Other, "resultant offset is negative")
128}
129
130#[cfg(test)]
131mod tests;