1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
use std::future::Future;
use bytes::Bytes;
use futures::{
future::{ready, Either},
stream::{once, unfold},
FutureExt, Stream, StreamExt,
};
use reqwest::{Client, Response, Url};
use tokio::io::{AsyncWrite, AsyncWriteExt};
use crate::{errors::DownloadError, net::file_url};
/// A trait for downloading files from Telegram.
pub trait Download<'w>
/* FIXME(waffle): ideally, this lifetime ('w) shouldn't be here, but we can't help it without
* GATs */
{
/// An error returned from [`download_file`](Self::download_file).
type Err;
/// A future returned from [`download_file`](Self::download_file).
type Fut: Future<Output = Result<(), Self::Err>> + Send;
/// Download a file from Telegram into `destination`.
///
/// `path` can be obtained from [`GetFile`].
///
/// To download as a stream of chunks, see [`download_file_stream`].
///
/// ## Examples
///
/// ```no_run
/// use teloxide_core::{
/// net::Download,
/// requests::{Request, Requester},
/// types::File,
/// Bot,
/// };
/// use tokio::fs;
///
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let bot = Bot::new("TOKEN");
///
/// let file = bot.get_file("*file_id*").await?;
/// let mut dst = fs::File::create("/tmp/test.png").await?;
/// bot.download_file(&file.path, &mut dst).await?;
/// # Ok(()) }
/// ```
///
/// [`GetFile`]: crate::payloads::GetFile
/// [`download_file_stream`]: Self::download_file_stream
fn download_file(
&self,
path: &str,
destination: &'w mut (dyn AsyncWrite + Unpin + Send),
) -> Self::Fut;
/// An error returned from
/// [`download_file_stream`](Self::download_file_stream).
type StreamErr;
/// A stream returned from [`download_file_stream`].
///
///[`download_file_stream`]: (Self::download_file_stream)
type Stream: Stream<Item = Result<Bytes, Self::StreamErr>> + Send;
/// Download a file from Telegram as [`Stream`].
///
/// `path` can be obtained from the [`GetFile`].
///
/// To download into an [`AsyncWrite`] (e.g. [`tokio::fs::File`]), see
/// [`download_file`].
///
/// [`GetFile`]: crate::payloads::GetFile
/// [`AsyncWrite`]: tokio::io::AsyncWrite
/// [`tokio::fs::File`]: tokio::fs::File
/// [`download_file`]: Self::download_file
fn download_file_stream(&self, path: &str) -> Self::Stream;
}
/// Download a file from Telegram into `dst`.
///
/// Note: if you don't need to use a different (from you're bot) client and
/// don't need to get *all* performance (and you don't, c'mon it's very io-bound
/// job), then it's recommended to use [`Download::download_file`].
pub fn download_file<'o, D>(
client: &Client,
api_url: Url,
token: &str,
path: &str,
dst: &'o mut D,
) -> impl Future<Output = Result<(), DownloadError>> + 'o
where
D: ?Sized + AsyncWrite + Unpin,
{
client.get(file_url(api_url, token, path)).send().then(move |r| async move {
let mut res = r?.error_for_status()?;
while let Some(chunk) = res.chunk().await? {
dst.write_all(&chunk).await?;
}
Ok(())
})
}
/// Download a file from Telegram as [`Stream`].
///
/// Note: if you don't need to use a different (from you're bot) client and
/// don't need to get *all* performance (and you don't, c'mon it's very io-bound
/// job), then it's recommended to use [`Download::download_file_stream`].
pub fn download_file_stream(
client: &Client,
api_url: Url,
token: &str,
path: &str,
) -> impl Stream<Item = reqwest::Result<Bytes>> + 'static {
client.get(file_url(api_url, token, path)).send().into_stream().flat_map(|res| {
match res.and_then(Response::error_for_status) {
Ok(res) => Either::Left(unfold(res, |mut res| async {
match res.chunk().await {
Err(err) => Some((Err(err), res)),
Ok(Some(c)) => Some((Ok(c), res)),
Ok(None) => None,
}
})),
Err(err) => Either::Right(once(ready(Err(err)))),
}
})
}