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
32use std::convert::{TryFrom, TryInto};
35use std::io::{self, Error, ErrorKind, Read, Result, Seek, SeekFrom, Write};
36
37pub struct ShallowTees<R: Read + Seek, W: Write> {
40 read: R,
41 write: W,
42 cur: u64,
44 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 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;