use core::future::poll_fn;
use core::pin::Pin;
use core::task::{Context, Poll, ready};
use std::io;
use futures_io::{AsyncBufRead, AsyncRead, AsyncWrite};
const DEFAULT_BUF_SIZE: usize = 8 * 1024;
#[derive(Debug)]
struct CopyBuffer {
read_done: bool,
need_flush: bool,
pos: usize,
cap: usize,
amount: u64,
buf: Box<[u8]>,
}
impl CopyBuffer {
fn new(size: usize) -> Self {
Self {
read_done: false,
need_flush: false,
pos: 0,
cap: 0,
amount: 0,
buf: vec![0; size.max(1)].into_boxed_slice(),
}
}
fn poll_copy<R, W>(
&mut self,
cx: &mut Context<'_>,
mut reader: Pin<&mut R>,
mut writer: Pin<&mut W>,
) -> Poll<io::Result<u64>>
where
R: AsyncRead + ?Sized,
W: AsyncWrite + ?Sized,
{
loop {
if self.pos == self.cap && !self.read_done {
match reader.as_mut().poll_read(cx, &mut self.buf) {
Poll::Ready(Ok(0)) => self.read_done = true,
Poll::Ready(Ok(n)) => {
self.pos = 0;
self.cap = n;
}
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
Poll::Pending => {
if self.need_flush {
ready!(writer.as_mut().poll_flush(cx))?;
self.need_flush = false;
}
return Poll::Pending;
}
}
}
while self.pos < self.cap {
let n = ready!(
writer
.as_mut()
.poll_write(cx, &self.buf[self.pos..self.cap])
)?;
if n == 0 {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::WriteZero,
"the writer took no more bytes",
)));
}
self.pos += n;
self.amount += n as u64;
self.need_flush = true;
}
if self.pos == self.cap && self.read_done {
ready!(writer.as_mut().poll_flush(cx))?;
return Poll::Ready(Ok(self.amount));
}
}
}
}
pub async fn copy<R, W>(reader: &mut R, writer: &mut W) -> io::Result<u64>
where
R: AsyncRead + Unpin + ?Sized,
W: AsyncWrite + Unpin + ?Sized,
{
let mut buf = CopyBuffer::new(DEFAULT_BUF_SIZE);
poll_fn(|cx| buf.poll_copy(cx, Pin::new(&mut *reader), Pin::new(&mut *writer))).await
}
pub async fn copy_buf<R, W>(reader: &mut R, writer: &mut W) -> io::Result<u64>
where
R: AsyncBufRead + Unpin + ?Sized,
W: AsyncWrite + Unpin + ?Sized,
{
let mut amount = 0u64;
poll_fn(|cx| {
loop {
let mut reader = Pin::new(&mut *reader);
let writer = Pin::new(&mut *writer);
let available = ready!(reader.as_mut().poll_fill_buf(cx))?;
if available.is_empty() {
ready!(writer.poll_flush(cx))?;
return Poll::Ready(Ok(amount));
}
let n = ready!(writer.poll_write(cx, available))?;
if n == 0 {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::WriteZero,
"the writer took no more bytes",
)));
}
reader.consume(n);
amount += n as u64;
}
})
.await
}
#[derive(Debug)]
enum Transfer {
Running(CopyBuffer),
ShuttingDown(u64),
Done(u64),
}
impl Transfer {
fn poll<R, W>(
&mut self,
cx: &mut Context<'_>,
reader: Pin<&mut R>,
mut writer: Pin<&mut W>,
) -> Poll<io::Result<u64>>
where
R: AsyncRead + ?Sized,
W: AsyncWrite + ?Sized,
{
if let Self::Running(buf) = self {
let amount = ready!(buf.poll_copy(cx, reader, writer.as_mut()))?;
*self = Self::ShuttingDown(amount);
}
self.poll_after_copy(cx, writer)
}
fn poll_after_copy<W: AsyncWrite + ?Sized>(
&mut self,
cx: &mut Context<'_>,
writer: Pin<&mut W>,
) -> Poll<io::Result<u64>> {
match *self {
Self::ShuttingDown(amount) => {
ready!(writer.poll_close(cx))?;
*self = Self::Done(amount);
Poll::Ready(Ok(amount))
}
Self::Done(amount) => Poll::Ready(Ok(amount)),
Self::Running(_) => Poll::Pending,
}
}
}
pub async fn copy_bidirectional<A, B>(a: &mut A, b: &mut B) -> io::Result<(u64, u64)>
where
A: AsyncRead + AsyncWrite + Unpin + ?Sized,
B: AsyncRead + AsyncWrite + Unpin + ?Sized,
{
copy_bidirectional_with_sizes(a, b, DEFAULT_BUF_SIZE, DEFAULT_BUF_SIZE).await
}
pub async fn copy_bidirectional_with_sizes<A, B>(
a: &mut A,
b: &mut B,
a_to_b: usize,
b_to_a: usize,
) -> io::Result<(u64, u64)>
where
A: AsyncRead + AsyncWrite + Unpin + ?Sized,
B: AsyncRead + AsyncWrite + Unpin + ?Sized,
{
let mut forward = Transfer::Running(CopyBuffer::new(a_to_b));
let mut backward = Transfer::Running(CopyBuffer::new(b_to_a));
poll_fn(|cx| {
let ab = forward.poll(cx, Pin::new(&mut *a), Pin::new(&mut *b))?;
let ba = backward.poll(cx, Pin::new(&mut *b), Pin::new(&mut *a))?;
match (ab, ba) {
(Poll::Ready(ab), Poll::Ready(ba)) => Poll::Ready(Ok((ab, ba))),
_ => Poll::Pending,
}
})
.await
}