git_packetline/read/sidebands/
blocking_io.rs

1use std::{io, io::BufRead};
2
3use crate::{BandRef, PacketLineRef, StreamingPeekableIter, TextRef, U16_HEX_BYTES};
4
5/// An implementor of [`BufRead`][io::BufRead] yielding packet lines on each call to [`read_line()`][io::BufRead::read_line()].
6/// It's also possible to hide the underlying packet lines using the [`Read`][io::Read] implementation which is useful
7/// if they represent binary data, like the one of a pack file.
8pub struct WithSidebands<'a, T, F>
9where
10    T: io::Read,
11{
12    parent: &'a mut StreamingPeekableIter<T>,
13    handle_progress: Option<F>,
14    pos: usize,
15    cap: usize,
16}
17
18impl<'a, T, F> Drop for WithSidebands<'a, T, F>
19where
20    T: io::Read,
21{
22    fn drop(&mut self) {
23        self.parent.reset();
24    }
25}
26
27impl<'a, T> WithSidebands<'a, T, fn(bool, &[u8])>
28where
29    T: io::Read,
30{
31    /// Create a new instance with the given provider as `parent`.
32    pub fn new(parent: &'a mut StreamingPeekableIter<T>) -> Self {
33        WithSidebands {
34            parent,
35            handle_progress: None,
36            pos: 0,
37            cap: 0,
38        }
39    }
40}
41
42impl<'a, T, F> WithSidebands<'a, T, F>
43where
44    T: io::Read,
45    F: FnMut(bool, &[u8]),
46{
47    /// Create a new instance with the given `parent` provider and the `handle_progress` function.
48    ///
49    /// Progress or error information will be passed to the given `handle_progress(is_error, text)` function, with `is_error: bool`
50    /// being true in case the `text` is to be interpreted as error.
51    pub fn with_progress_handler(parent: &'a mut StreamingPeekableIter<T>, handle_progress: F) -> Self {
52        WithSidebands {
53            parent,
54            handle_progress: Some(handle_progress),
55            pos: 0,
56            cap: 0,
57        }
58    }
59
60    /// Create a new instance without a progress handler.
61    pub fn without_progress_handler(parent: &'a mut StreamingPeekableIter<T>) -> Self {
62        WithSidebands {
63            parent,
64            handle_progress: None,
65            pos: 0,
66            cap: 0,
67        }
68    }
69
70    /// Forwards to the parent [StreamingPeekableIter::reset_with()]
71    pub fn reset_with(&mut self, delimiters: &'static [PacketLineRef<'static>]) {
72        self.parent.reset_with(delimiters)
73    }
74
75    /// Forwards to the parent [StreamingPeekableIter::stopped_at()]
76    pub fn stopped_at(&self) -> Option<PacketLineRef<'static>> {
77        self.parent.stopped_at
78    }
79
80    /// Set or unset the progress handler.
81    pub fn set_progress_handler(&mut self, handle_progress: Option<F>) {
82        self.handle_progress = handle_progress;
83    }
84
85    /// Effectively forwards to the parent [StreamingPeekableIter::peek_line()], allowing to see what would be returned
86    /// next on a call to [`read_line()`][io::BufRead::read_line()].
87    ///
88    /// # Warning
89    ///
90    /// This skips all sideband handling and may return an unprocessed line with sidebands still contained in it.
91    pub fn peek_data_line(&mut self) -> Option<io::Result<Result<&[u8], crate::decode::Error>>> {
92        match self.parent.peek_line() {
93            Some(Ok(Ok(PacketLineRef::Data(line)))) => Some(Ok(Ok(line))),
94            Some(Ok(Err(err))) => Some(Ok(Err(err))),
95            Some(Err(err)) => Some(Err(err)),
96            _ => None,
97        }
98    }
99
100    /// Read a whole packetline from the underlying reader, with empty lines indicating a stop packetline.
101    ///
102    /// # Warning
103    ///
104    /// This skips all sideband handling and may return an unprocessed line with sidebands still contained in it.
105    pub fn read_data_line(&mut self) -> Option<io::Result<Result<PacketLineRef<'_>, crate::decode::Error>>> {
106        assert_eq!(
107            self.cap, 0,
108            "we don't support partial buffers right now - read-line must be used consistently"
109        );
110        self.parent.read_line()
111    }
112}
113
114impl<'a, T, F> BufRead for WithSidebands<'a, T, F>
115where
116    T: io::Read,
117    F: FnMut(bool, &[u8]),
118{
119    fn fill_buf(&mut self) -> io::Result<&[u8]> {
120        if self.pos >= self.cap {
121            let (ofs, cap) = loop {
122                let line = match self.parent.read_line() {
123                    Some(line) => line?.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?,
124                    None => break (0, 0),
125                };
126                match self.handle_progress.as_mut() {
127                    Some(handle_progress) => {
128                        let band = line
129                            .decode_band()
130                            .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
131                        const ENCODED_BAND: usize = 1;
132                        match band {
133                            BandRef::Data(d) => {
134                                if d.is_empty() {
135                                    continue;
136                                }
137                                break (U16_HEX_BYTES + ENCODED_BAND, d.len());
138                            }
139                            BandRef::Progress(d) => {
140                                let text = TextRef::from(d).0;
141                                handle_progress(false, text);
142                            }
143                            BandRef::Error(d) => {
144                                let text = TextRef::from(d).0;
145                                handle_progress(true, text);
146                            }
147                        };
148                    }
149                    None => {
150                        break match line.as_slice() {
151                            Some(d) => (U16_HEX_BYTES, d.len()),
152                            None => {
153                                return Err(io::Error::new(
154                                    io::ErrorKind::UnexpectedEof,
155                                    "encountered non-data line in a data-line only context",
156                                ))
157                            }
158                        }
159                    }
160                }
161            };
162            self.cap = cap + ofs;
163            self.pos = ofs;
164        }
165        Ok(&self.parent.buf[self.pos..self.cap])
166    }
167
168    fn consume(&mut self, amt: usize) {
169        self.pos = std::cmp::min(self.pos + amt, self.cap);
170    }
171
172    fn read_line(&mut self, buf: &mut String) -> io::Result<usize> {
173        assert_eq!(
174            self.cap, 0,
175            "we don't support partial buffers right now - read-line must be used consistently"
176        );
177        let line = std::str::from_utf8(self.fill_buf()?).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
178        buf.push_str(line);
179        let bytes = line.len();
180        self.cap = 0;
181        Ok(bytes)
182    }
183}
184
185impl<'a, T, F> io::Read for WithSidebands<'a, T, F>
186where
187    T: io::Read,
188    F: FnMut(bool, &[u8]),
189{
190    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
191        let nread = {
192            let mut rem = self.fill_buf()?;
193            rem.read(buf)?
194        };
195        self.consume(nread);
196        Ok(nread)
197    }
198}