use std::io::{Error as IoError, ErrorKind, IoSlice, Result};
use std::mem;
use std::pin::Pin;
use std::task::{Context, Poll, ready};
use tokio::io::{AsyncBufRead, AsyncRead, ReadBuf};
use crate::shared::block::{Block, Header};
use crate::shared::buffer::ReadableRegion;
use crate::shared::slices::IntoBuffersIterator;
use crate::shared::state::State;
use crate::{Archive, BLOCK_SIZE, Entry, TRACING_ENABLED};
impl<R: AsyncRead> Archive<R> {
fn poll_next_state(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
header: Option<&Header>,
) -> Poll<Result<(State, usize)>> {
ready!(self.as_mut().poll_fill_buf(cx))?;
let this = self.as_mut().project();
let buf = this.buf.buffered_bytes();
Poll::Ready(this.state.next(buf, header))
}
fn poll_next_entry(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<Option<Entry<'_, R>>>> {
if self.state.is_terminal() {
return Poll::Ready(Ok(None));
}
loop {
if TRACING_ENABLED {
eprintln!(" |entry: {:?}", self.state);
}
let (state, amt) = ready!(self.as_mut().poll_next_state(cx, None))?;
match state {
State::ReceivedHeader => {
let this = self.as_mut().project();
let buf = this.buf.buffered_bytes();
let block = Block::from_bytes(&buf[..BLOCK_SIZE]);
let header = block.as_header()?.to_owned();
self.as_mut().consume(amt, Some(&header));
let entry = Entry::new(self, header)?;
return Poll::Ready(Ok(Some(entry)));
}
State::ReceivedEof => {
self.consume(amt, None);
return Poll::Ready(Ok(None));
}
State::ReceivingHeader(_, _) | State::ReceivingEof(_) => {
self.as_mut().consume(amt, None);
continue;
}
State::AligningData(_) | State::AlignedData => {
self.as_mut().consume(amt, None);
continue;
}
s => {
panic!("cannot read next entry while another entry is being read ({s:?})");
}
}
}
}
fn poll_read_entry(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
header: &Header,
) -> Poll<Result<&[u8]>> {
loop {
if TRACING_ENABLED {
eprintln!(" |read: {:?}", self.state);
}
let (state, amt) = ready!(self.as_mut().poll_next_state(cx, Some(header)))?;
match state {
State::ReceivingData(_) | State::ReceivedData => {
let this = self.project();
let buf = this.buf.buffered_bytes();
return Poll::Ready(Ok(&buf[..amt]));
}
State::AligningData(_) => {
self.as_mut().consume(amt, None);
continue;
}
State::AlignedData => {
self.as_mut().consume(amt, Some(header));
return Poll::Ready(Ok(&[]));
}
s => {
unreachable!("cannot read entry: invalid state: {s:?}");
}
}
}
}
fn poll_skip_entry(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
header: &Header,
) -> Poll<Result<()>> {
loop {
let buf = ready!(self.as_mut().poll_read_entry(cx, header))?;
let amt = buf.len();
if amt == 0 {
assert_eq!(self.state, State::ExpectingHeader);
return Poll::Ready(Ok(()));
}
self.as_mut().consume(amt, Some(header));
}
}
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
let mut this = self.project();
if this.buf.buffered_bytes().is_empty() {
let buf = this.buf.available_bytes_mut();
let mut buf = ReadBuf::new(buf);
ready!(this.io.as_mut().poll_read(cx, &mut buf))?;
let bytes_read = buf.filled().len();
if bytes_read == 0 {
assert!(!this.buf.available_bytes_mut().is_empty());
if !this.state.is_terminal() {
let err = IoError::from(ErrorKind::UnexpectedEof);
return Poll::Ready(Err(err));
}
}
this.buf.available().commit(bytes_read);
}
Poll::Ready(Ok(()))
}
fn consume(self: Pin<&mut Self>, amt: usize, header: Option<&Header>) {
let this = self.project();
let mut buffered = this.buf.buffered();
let available = buffered.len();
assert!(
available >= amt,
"cannot consume more than available; amt = {amt}, available = {available}",
);
let slices = [IoSlice::new(&buffered.bytes()[..amt])];
let (state, pos) = this
.state
.take_slices(slices.iter().into_buffers(), header)
.expect("this slice should have already been checked");
let state = match state {
State::ReceivingHeader(0, false)
| State::ReceivedHeader
| State::ReceivingData(0)
| State::ReceivedData
| State::AligningData(0)
| State::AlignedData
| State::ReceivingEof(0) => {
state.next(&[], header).unwrap().0
}
_ => state,
};
assert_eq!(
pos, amt,
"cannot consume past another entry; amt = {amt}, offset = {pos}",
);
if TRACING_ENABLED {
eprintln!(" | cnsm: {amt} / {:?} -> {state:?}", *this.state);
}
*this.state = state;
buffered.commit(amt);
if buffered.is_empty() {
this.buf.clear();
}
}
}
impl<R: AsyncRead> AsyncRead for Entry<'_, R> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<Result<()>> {
if buf.remaining() == 0 {
return Poll::Ready(Ok(()));
}
let bytes = ready!(self.as_mut().poll_fill_buf(cx))?;
let len = bytes.len().min(buf.remaining());
buf.put_slice(&bytes[..len]);
self.consume(len);
Poll::Ready(Ok(()))
}
}
impl<R: AsyncRead> AsyncBufRead for Entry<'_, R> {
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<&[u8]>> {
if TRACING_ENABLED {
eprintln!(" fill: '{}', size = {}", self.path_lossy(), self.size());
}
let this = self.project();
let header = this.header;
this.archive.as_mut().poll_read_entry(cx, header)
}
fn consume(self: Pin<&mut Self>, amt: usize) {
if TRACING_ENABLED {
eprintln!("consm: '{}', size = {}", self.path_lossy(), self.size());
}
let this = self.project();
let header = this.header;
this.archive.as_mut().consume(amt, Some(header));
}
}
impl<R: AsyncRead> Entry<'_, R> {
pub(super) fn poll_skip(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
if TRACING_ENABLED {
eprintln!(" skip: '{}', size = {}", self.path_lossy(), self.size());
}
let this = self.project();
let header = this.header;
this.archive.as_mut().poll_skip_entry(cx, header)
}
}
#[derive(Debug)]
pub struct NextEntry<'a, R>(&'a mut Archive<R>);
impl<'a, R> NextEntry<'a, R> {
pub(super) fn new(archive: &'a mut Archive<R>) -> Self {
Self(archive)
}
}
impl<'a, R> Future for NextEntry<'a, R>
where
R: AsyncRead + Unpin,
{
type Output = Result<Option<Entry<'a, R>>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let pin = Pin::new(&mut *self.get_mut().0);
if let Some(entry) = ready!(pin.poll_next_entry(cx))? {
let entry = unsafe {
mem::transmute::<Entry<'_, R>, Entry<'a, R>>(entry)
};
Poll::Ready(Ok(Some(entry)))
} else {
Poll::Ready(Ok(None))
}
}
}
#[cfg(test)]
mod tests;