use crate::error::ParseError;
use crate::iterators::ConFrameIterator;
use crate::types::ConFrame;
use std::io::{self, Read};
use std::path::Path;
pub type StreamError = Box<dyn std::error::Error + Send + Sync>;
pub const DEFAULT_READ_SIZE: usize = 1 << 20;
pub struct ConFrameReader<R> {
src: R,
buf: Vec<u8>,
start: usize,
end: usize,
read_size: usize,
eof: bool,
failed: Option<io::Error>,
done: bool,
}
impl ConFrameReader<Box<dyn Read + Send>> {
pub fn open(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
Ok(Self::new(crate::compression::open_decoded(path)?))
}
}
impl<R: Read> ConFrameReader<R> {
pub fn new(src: R) -> Self {
Self::with_read_size(src, DEFAULT_READ_SIZE)
}
pub fn with_read_size(src: R, read_size: usize) -> Self {
Self {
src,
buf: Vec::new(),
start: 0,
end: 0,
read_size: read_size.max(1),
eof: false,
failed: None,
done: false,
}
}
pub fn forward(&mut self) -> Option<Result<(), StreamError>> {
self.step(|it| it.forward().map(|r| r.map(|()| ((), it.byte_pos()))))
}
fn step<T>(
&mut self,
mut op: impl FnMut(&mut ConFrameIterator<'_>) -> Option<Result<(T, usize), ParseError>>,
) -> Option<Result<T, StreamError>> {
let mut cut = false;
while !self.done {
let ended = self.eof || self.failed.is_some();
let rest = &self.buf[self.start..self.end];
let text = if ended {
rest
} else {
memchr::memrchr(b'\n', rest).map_or(&rest[..0], |i| &rest[..=i])
};
if ended || !cut || {
let mut probe = ConFrameIterator::from_bytes(text);
probe.forward();
settled(text, probe.byte_pos())
} {
let mut it = ConFrameIterator::from_bytes(text);
let result = op(&mut it);
if ended || settled(text, it.byte_pos()) {
match result {
Some(Ok((value, used))) => {
self.start += used;
return Some(Ok(value));
}
other if self.failed.is_none() => {
self.done = true;
return other.map(|r| r.map(|(value, _)| value).map_err(Into::into));
}
_ => {}
}
}
}
cut = true;
if let Some(e) = self.failed.take() {
self.done = true;
return Some(Err(e.into()));
}
self.fill();
}
None
}
fn fill(&mut self) {
self.buf.copy_within(self.start..self.end, 0);
self.end -= self.start;
self.start = 0;
let target = self.end + self.read_size.max(self.end / 8);
if target > self.buf.len() {
self.buf.reserve_exact(target - self.buf.len());
self.buf.resize(target, 0);
}
while self.end < target {
match self.src.read(&mut self.buf[self.end..target]) {
Ok(0) => {
self.eof = true;
return;
}
Ok(n) => self.end += n,
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => {
self.failed = Some(e);
return;
}
}
}
}
}
fn settled(text: &[u8], pos: usize) -> bool {
let mut lines = text[pos.min(text.len())..].split_inclusive(|&b| b == b'\n');
lines
.find(|line| !line.iter().all(u8::is_ascii_whitespace))
.is_some()
&& lines.next().is_some()
}
impl<R: Read> Iterator for ConFrameReader<R> {
type Item = Result<ConFrame, StreamError>;
fn next(&mut self) -> Option<Self::Item> {
self.step(|it| it.next_with_end())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn zero_atom_frame(len: usize) -> Vec<u8> {
let tail = "\n{\"con_spec_version\":2}\n10.000000 10.000000 10.000000\n\
90.000000 90.000000 90.000000\n\n\n0\n\n\n";
let mut frame = vec![b'U'; len - tail.len()];
frame.extend_from_slice(tail.as_bytes());
frame
}
fn after_first(frame: &[u8], after: &[u8]) -> (usize, usize) {
let text = [frame, after].concat();
let mut reader = ConFrameReader::new(&text[..]);
let want = ConFrameIterator::from_bytes(frame).next().unwrap().unwrap();
assert_eq!(reader.next().unwrap().unwrap(), want);
assert_eq!(reader.start, frame.len());
(reader.buf.len(), reader.buf.capacity())
}
#[test]
fn a_frame_over_one_read_grows_the_buffer_to_about_one_frame_and_one_read() {
let small = zero_atom_frame(100).repeat(2);
for len in [(1 << 20) + 57, (1 << 21) + 57] {
let frame = zero_atom_frame(len);
for after in [&small[..], &[][..]] {
let (filled, capacity) = after_first(&frame, after);
assert!(filled <= capacity);
assert!(
capacity <= len + DEFAULT_READ_SIZE,
"frame {len}, {} bytes after it: capacity {capacity}",
after.len()
);
}
}
}
#[test]
fn a_frame_of_many_reads_grows_the_buffer_by_an_eighth_at_most() {
let read_size = 4096;
let len = 64 * read_size + 57;
let text = [zero_atom_frame(len), zero_atom_frame(100)]
.concat()
.repeat(2);
let mut reader = ConFrameReader::with_read_size(&text[..], read_size);
for _ in 0..4 {
assert!(reader.next().unwrap().is_ok());
assert!(reader.buf.capacity() <= len + len / 8 + read_size);
}
assert!(reader.next().is_none());
}
}