use std::{marker::PhantomData, pin::Pin};
use bytes::{BufMut, BytesMut};
use futures::{
ready,
stream::{FusedStream, Stream},
};
use pin_project_lite::pin_project;
use rusoto_core::ByteStream;
use rusoto_s3::UploadPartRequest;
pub trait StreamUploadParts {
fn upload_parts<I, E>(
self,
part_template: UploadPartRequest,
minimum_part_size: usize,
) -> UploadParts<Self, E>
where
Self: Stream<Item = Result<I, E>> + Sized,
I: std::borrow::Borrow<[u8]>;
}
impl<S> StreamUploadParts for S {
fn upload_parts<I, E>(
self,
part_template: UploadPartRequest,
minimum_part_size: usize,
) -> UploadParts<Self, E>
where
S: Stream<Item = Result<I, E>>,
I: std::borrow::Borrow<[u8]>,
{
UploadParts::new(self, part_template, minimum_part_size)
}
}
pin_project! {
pub struct UploadParts<S, E> {
#[pin]
stream: S,
input: BytesMut,
next_part_number: i64,
finished: bool,
part_template: UploadPartRequest,
minimum_part_size: usize,
error_type: PhantomData<E>,
}
}
impl<S, E> UploadParts<S, E> {
fn new(stream: S, part_template: UploadPartRequest, minimum_part_size: usize) -> Self
where
S: Stream,
{
Self {
stream,
input: BytesMut::new(),
next_part_number: 1,
finished: false,
part_template,
minimum_part_size,
error_type: PhantomData,
}
}
fn next_input(
self: &mut Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<S::Item>>
where
S: Stream,
{
self.as_mut().project().stream.poll_next(cx)
}
fn part_from_buffer(self: &mut Pin<&mut Self>) -> UploadPartRequest {
let this = self.as_mut().project();
let buffer: &mut BytesMut = this.input;
let part_template: &UploadPartRequest = this.part_template;
let req = UploadPartRequest {
body: Some(ByteStream::from(Vec::from(&buffer[..]))),
bucket: part_template.bucket.to_owned(),
content_length: None,
content_md5: part_template.content_md5.to_owned(),
expected_bucket_owner: part_template.expected_bucket_owner.to_owned(),
key: part_template.key.to_owned(),
part_number: *this.next_part_number,
request_payer: part_template.request_payer.to_owned(),
sse_customer_algorithm: part_template.sse_customer_algorithm.to_owned(),
sse_customer_key: part_template.sse_customer_key.to_owned(),
sse_customer_key_md5: part_template.sse_customer_key_md5.to_owned(),
upload_id: part_template.upload_id.to_owned(),
};
*this.next_part_number += 1;
buffer.clear();
req
}
fn accept_input(self: &mut Pin<&mut Self>, input: &[u8]) -> Option<UploadPartRequest> {
let this = self.as_mut().project();
let buffer: &mut BytesMut = this.input;
buffer.put(input);
if buffer.len() >= *this.minimum_part_size {
Some(self.part_from_buffer())
} else {
None
}
}
fn end_stream(self: &mut Pin<&mut Self>) -> Option<UploadPartRequest> {
let this = self.as_mut().project();
*this.finished = true;
let buffer: &BytesMut = this.input;
if !buffer.is_empty() {
Some(self.part_from_buffer())
} else {
None
}
}
fn finished(self: &mut Pin<&mut Self>) -> bool {
*self.as_mut().project().finished
}
}
impl<S, I, E> Stream for UploadParts<S, E>
where
S: Stream<Item = Result<I, E>>,
I: std::borrow::Borrow<[u8]>,
{
type Item = Result<UploadPartRequest, E>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
if self.finished() {
return std::task::Poll::Ready(None);
}
std::task::Poll::Ready(loop {
match ready!(self.next_input(cx)) {
None => break self.end_stream().map(Ok),
Some(Err(e)) => break Some(Err(e)),
Some(Ok(bytes)) => {
if let Some(part) = self.accept_input(bytes.borrow()) {
break Some(Ok(part));
}
}
}
})
}
}
impl<S, I, E> FusedStream for UploadParts<S, E>
where
S: Stream<Item = Result<I, E>>,
I: std::borrow::Borrow<[u8]>,
{
fn is_terminated(&self) -> bool {
self.finished
}
}