blob: dec23a9baa7afc0702a274d60786faa6b3b3a1de [file] [log] [blame]
use std::{io, io::BufRead};
use crate::{read::ProgressAction, BandRef, PacketLineRef, StreamingPeekableIter, TextRef, U16_HEX_BYTES};
/// An implementor of [`BufRead`][io::BufRead] yielding packet lines on each call to [`read_line()`][io::BufRead::read_line()].
/// It's also possible to hide the underlying packet lines using the [`Read`][io::Read] implementation which is useful
/// if they represent binary data, like the one of a pack file.
pub struct WithSidebands<'a, T, F>
where
T: io::Read,
{
parent: &'a mut StreamingPeekableIter<T>,
handle_progress: Option<F>,
pos: usize,
cap: usize,
}
impl<'a, T, F> Drop for WithSidebands<'a, T, F>
where
T: io::Read,
{
fn drop(&mut self) {
self.parent.reset();
}
}
impl<'a, T> WithSidebands<'a, T, fn(bool, &[u8]) -> ProgressAction>
where
T: io::Read,
{
/// Create a new instance with the given provider as `parent`.
pub fn new(parent: &'a mut StreamingPeekableIter<T>) -> Self {
WithSidebands {
parent,
handle_progress: None,
pos: 0,
cap: 0,
}
}
}
impl<'a, T, F> WithSidebands<'a, T, F>
where
T: io::Read,
F: FnMut(bool, &[u8]) -> ProgressAction,
{
/// Create a new instance with the given `parent` provider and the `handle_progress` function.
///
/// Progress or error information will be passed to the given `handle_progress(is_error, text)` function, with `is_error: bool`
/// being true in case the `text` is to be interpreted as error.
pub fn with_progress_handler(parent: &'a mut StreamingPeekableIter<T>, handle_progress: F) -> Self {
WithSidebands {
parent,
handle_progress: Some(handle_progress),
pos: 0,
cap: 0,
}
}
/// Create a new instance without a progress handler.
pub fn without_progress_handler(parent: &'a mut StreamingPeekableIter<T>) -> Self {
WithSidebands {
parent,
handle_progress: None,
pos: 0,
cap: 0,
}
}
/// Forwards to the parent [`StreamingPeekableIter::reset_with()`]
pub fn reset_with(&mut self, delimiters: &'static [PacketLineRef<'static>]) {
self.parent.reset_with(delimiters)
}
/// Forwards to the parent [`StreamingPeekableIter::stopped_at()`]
pub fn stopped_at(&self) -> Option<PacketLineRef<'static>> {
self.parent.stopped_at
}
/// Set or unset the progress handler.
pub fn set_progress_handler(&mut self, handle_progress: Option<F>) {
self.handle_progress = handle_progress;
}
/// Effectively forwards to the parent [`StreamingPeekableIter::peek_line()`], allowing to see what would be returned
/// next on a call to [`read_line()`][io::BufRead::read_line()].
///
/// # Warning
///
/// This skips all sideband handling and may return an unprocessed line with sidebands still contained in it.
pub fn peek_data_line(&mut self) -> Option<io::Result<Result<&[u8], crate::decode::Error>>> {
match self.parent.peek_line() {
Some(Ok(Ok(PacketLineRef::Data(line)))) => Some(Ok(Ok(line))),
Some(Ok(Err(err))) => Some(Ok(Err(err))),
Some(Err(err)) => Some(Err(err)),
_ => None,
}
}
/// Read a whole packetline from the underlying reader, with empty lines indicating a stop packetline.
///
/// # Warning
///
/// This skips all sideband handling and may return an unprocessed line with sidebands still contained in it.
pub fn read_data_line(&mut self) -> Option<io::Result<Result<PacketLineRef<'_>, crate::decode::Error>>> {
assert_eq!(
self.cap, 0,
"we don't support partial buffers right now - read-line must be used consistently"
);
self.parent.read_line()
}
/// Like `BufRead::read_line()`, but will only read one packetline at a time.
///
/// It will also be easier to call as sometimes it's unclear which implementation we get on a type like this with
/// plenty of generic parameters.
pub fn read_line_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
assert_eq!(
self.cap, 0,
"we don't support partial buffers right now - read-line must be used consistently"
);
let line = std::str::from_utf8(self.fill_buf()?).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
buf.push_str(line);
let bytes = line.len();
self.cap = 0;
Ok(bytes)
}
}
impl<'a, T, F> BufRead for WithSidebands<'a, T, F>
where
T: io::Read,
F: FnMut(bool, &[u8]) -> ProgressAction,
{
fn fill_buf(&mut self) -> io::Result<&[u8]> {
if self.pos >= self.cap {
let (ofs, cap) = loop {
let line = match self.parent.read_line() {
Some(line) => line?.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?,
None => break (0, 0),
};
match self.handle_progress.as_mut() {
Some(handle_progress) => {
let band = line
.decode_band()
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
const ENCODED_BAND: usize = 1;
match band {
BandRef::Data(d) => {
if d.is_empty() {
continue;
}
break (U16_HEX_BYTES + ENCODED_BAND, d.len());
}
BandRef::Progress(d) => {
let text = TextRef::from(d).0;
match handle_progress(false, text) {
ProgressAction::Continue => {}
ProgressAction::Interrupt => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"interrupted by user",
))
}
};
}
BandRef::Error(d) => {
let text = TextRef::from(d).0;
match handle_progress(true, text) {
ProgressAction::Continue => {}
ProgressAction::Interrupt => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"interrupted by user",
))
}
};
}
};
}
None => {
break match line.as_slice() {
Some(d) => (U16_HEX_BYTES, d.len()),
None => {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"encountered non-data line in a data-line only context",
))
}
}
}
}
};
self.cap = cap + ofs;
self.pos = ofs;
}
Ok(&self.parent.buf[self.pos..self.cap])
}
fn consume(&mut self, amt: usize) {
self.pos = std::cmp::min(self.pos + amt, self.cap);
}
}
impl<'a, T, F> io::Read for WithSidebands<'a, T, F>
where
T: io::Read,
F: FnMut(bool, &[u8]) -> ProgressAction,
{
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut rem = self.fill_buf()?;
let nread = rem.read(buf)?;
self.consume(nread);
Ok(nread)
}
}