use std::{
io::{self, Read},
sync::Arc,
};
use crate::{
sans_io::{
decompressor::Decompressor, LinearReaderOptions, MessageReadEvent,
MessageReader as SansIoReader,
},
Channel, McapResult, Message,
};
pub struct MessageReader<R> {
source: R,
reader: SansIoReader,
done: bool,
}
impl<R: Read> MessageReader<R> {
pub fn new(source: R) -> Self {
Self {
source,
reader: SansIoReader::new(),
done: false,
}
}
pub fn new_with_options(source: R, options: LinearReaderOptions) -> Self {
Self {
source,
reader: SansIoReader::new_with_options(options),
done: false,
}
}
pub fn add_decompressor(
&mut self,
decompressor: impl Decompressor + 'static,
) -> McapResult<()> {
self.reader.add_decompressor(decompressor)
}
pub fn get_channel(&self, channel_id: u16) -> Option<Arc<Channel<'static>>> {
self.reader.get_channel(channel_id)
}
pub fn into_inner(self) -> R {
self.source
}
}
impl<R: Read> Iterator for MessageReader<R> {
type Item = McapResult<Message<'static>>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
while let Some(event) = self.reader.next_event() {
match event {
Ok(MessageReadEvent::ReadRequest(need)) => {
let read = loop {
match self.source.read(self.reader.insert(need)) {
Ok(n) => break n,
Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
Err(err) => {
self.done = true;
return Some(Err(err.into()));
}
}
};
self.reader.notify_read(read);
}
Ok(MessageReadEvent::Message(message)) => return Some(Ok(message)),
Err(err) => {
self.done = true;
return Some(Err(err));
}
}
}
self.done = true;
None
}
}
#[cfg(test)]
mod tests {
use std::io::{self, Cursor, Read};
use super::*;
use crate::sans_io::message_reader::test_support::{
payload, truncated_in_last_message, two_channel_mcap,
};
use crate::McapError;
#[test]
fn matches_message_stream() {
for use_chunks in [false, true] {
let mcap = two_channel_mcap(use_chunks);
let expected = crate::MessageStream::new(&mcap)
.expect("stream")
.collect::<McapResult<Vec<_>>>()
.expect("messages");
let actual = MessageReader::new(Cursor::new(&mcap))
.collect::<McapResult<Vec<_>>>()
.expect("messages");
assert_eq!(actual, expected, "use_chunks={use_chunks}");
assert_eq!(actual[2].data, payload(3));
}
}
struct OneByteAtATime<'a>(&'a [u8]);
impl Read for OneByteAtATime<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = 1.min(buf.len()).min(self.0.len());
buf[..n].copy_from_slice(&self.0[..n]);
self.0 = &self.0[n..];
Ok(n)
}
}
#[test]
fn tolerates_short_reads() {
let mcap = two_channel_mcap(true);
let messages = MessageReader::new(OneByteAtATime(&mcap))
.collect::<McapResult<Vec<_>>>()
.expect("messages");
assert_eq!(messages.len(), 4);
}
#[test]
fn ends_after_the_first_error_even_when_errors_are_ignored() {
let mcap = two_channel_mcap(false);
let truncated = truncated_in_last_message(&mcap);
let count = MessageReader::new(Cursor::new(truncated)).flatten().count();
assert_eq!(count, 3, "the cut message is lost, the rest survive");
let items: Vec<_> = MessageReader::new(Cursor::new(truncated)).collect();
assert!(matches!(items.last(), Some(Err(McapError::UnexpectedEof))));
assert_eq!(items.iter().filter(|item| item.is_err()).count(), 1);
}
struct InterruptEveryOther<'a> {
bytes: &'a [u8],
interrupt_next: bool,
}
impl Read for InterruptEveryOther<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.interrupt_next = !self.interrupt_next;
if self.interrupt_next {
return Err(io::Error::from(io::ErrorKind::Interrupted));
}
let n = buf.len().min(self.bytes.len());
buf[..n].copy_from_slice(&self.bytes[..n]);
self.bytes = &self.bytes[n..];
Ok(n)
}
}
#[test]
fn retries_interrupted_reads() {
let mcap = two_channel_mcap(true);
let messages = MessageReader::new(InterruptEveryOther {
bytes: &mcap,
interrupt_next: false,
})
.collect::<McapResult<Vec<_>>>()
.expect("interruptions are retried, not surfaced");
assert_eq!(messages.len(), 4);
}
#[test]
fn reads_through_the_summary_to_the_end_magic() {
let mcap = two_channel_mcap(true);
let summary_start = crate::read::footer(&mcap).expect("footer").summary_start as usize;
let mut reader = MessageReader::new(Cursor::new(&mcap));
let messages = reader
.by_ref()
.collect::<McapResult<Vec<_>>>()
.expect("messages");
assert_eq!(messages.len(), 4);
assert_eq!(reader.into_inner().position() as usize, mcap.len());
let truncated = &mcap[..summary_start + 20];
let items = MessageReader::new(Cursor::new(truncated)).collect::<Vec<_>>();
assert_eq!(items.len(), 5, "{items:?}");
assert!(matches!(items[4], Err(McapError::UnexpectedEof)));
let messages = MessageReader::new_with_options(
Cursor::new(truncated),
LinearReaderOptions::default().with_skip_end_magic(true),
)
.collect::<McapResult<Vec<_>>>()
.expect("messages");
assert_eq!(messages.len(), 4);
}
struct FailAfter<'a> {
bytes: &'a [u8],
ok_bytes: usize,
}
impl Read for FailAfter<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.ok_bytes == 0 {
return Err(io::Error::other("disk on fire"));
}
let n = buf.len().min(self.ok_bytes).min(self.bytes.len());
buf[..n].copy_from_slice(&self.bytes[..n]);
self.bytes = &self.bytes[n..];
self.ok_bytes -= n;
Ok(n)
}
}
#[test]
fn surfaces_source_errors_once() {
let mcap = two_channel_mcap(false);
let items: Vec<_> = MessageReader::new(FailAfter {
bytes: &mcap,
ok_bytes: mcap.len() / 2,
})
.collect();
assert!(
matches!(items.last(), Some(Err(McapError::Io(_)))),
"{items:?}"
);
assert_eq!(items.iter().filter(|item| item.is_err()).count(), 1);
}
}