use std::fmt;
use std::marker::PhantomData;
use std::path::Path;
use bytes::Bytes;
use futures_util::StreamExt;
use http::HeaderMap;
use http_body_util::{BodyDataStream, BodyExt};
use tokio::fs::File;
use tokio::io::{AsyncWriteExt, BufWriter};
mod action;
mod error;
mod info;
pub use action::{DownloadAction, ResolvePolicy};
pub use error::{Error, ErrorKind};
pub use info::Info;
use crate::client::service::{Body, Response};
use crate::request::body::Body as RequestBody;
use crate::Client;
pub trait Download: private::Sealed {
fn download<A>(&self, action: A) -> Downloader<'_, Init<'_>>
where
DownloadAction: From<A>;
}
impl Download for Client {
fn download<A>(&self, action: A) -> Downloader<'_, Init<'_>>
where
DownloadAction: From<A>,
{
Downloader::<Init<'_>>::new(self, action.into())
}
}
pub struct Downloader<'a, State> {
state: State,
phantom: PhantomData<fn(&'a State) -> State>,
}
impl<T> Downloader<'_, T> {
pub(crate) fn new(http: &Client, action: DownloadAction) -> Downloader<'_, Init<'_>> {
Downloader {
state: Init { http, action },
phantom: PhantomData,
}
}
}
#[doc(hidden)]
pub struct Init<'a> {
http: &'a Client,
action: DownloadAction,
}
impl<'a> Downloader<'a, Init<'a>> {
pub async fn chunked(self) -> Result<Downloader<'a, Chunked>, Error> {
let Init { http, action } = self.state;
let info = info::download_info(http, action).await?;
let req = http::Request::get(info.download_url.as_str())
.body(RequestBody::empty())
.map_err(Error::request)?;
let response = http.raw_request(req).await.map_err(Error::request)?;
Ok(Downloader {
state: Chunked::new(info, response),
phantom: PhantomData,
})
}
pub async fn save_to_file(self, path: impl AsRef<Path>) -> Result<(), Error> {
let mut chunked = self.chunked().await?;
let out = File::create(path)
.await
.map_err(|err| Error::new(ErrorKind::Io).with(err))?;
let mut out = BufWriter::with_capacity(512 * 512, out);
while let Some(chunk) = chunked.data().await {
out.write_all(&chunk?)
.await
.map_err(|err| Error::new(ErrorKind::Io).with(err))?;
}
Ok(())
}
}
#[doc(hidden)]
pub struct Chunked {
info: Info,
headers: HeaderMap,
body: BodyDataStream<Body>,
}
impl Chunked {
fn new(info: Info, response: Response) -> Self {
let (parts, body) = response.into_parts();
let headers = parts.headers;
let body = body.into_data_stream();
Self {
info,
headers,
body,
}
}
}
impl Downloader<'_, Chunked> {
pub fn info(&self) -> &Info {
&self.state.info
}
pub fn headers(&self) -> &HeaderMap {
&self.state.headers
}
pub async fn data(&mut self) -> Option<Result<Bytes, Error>> {
let chunk = self.state.body.next().await;
chunk.map(|c| c.map_err(Error::body))
}
}
impl<'a> fmt::Debug for Downloader<'a, Init<'a>> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Downloader")
.field("action", &self.state.action)
.finish_non_exhaustive()
}
}
impl fmt::Debug for Downloader<'_, Chunked> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Downloader")
.field("info", &self.state.info)
.finish_non_exhaustive()
}
}
mod private {
use crate::client::Client;
pub trait Sealed {}
impl Sealed for Client {}
}