diff --git a/src/client/object.rs b/src/client/object.rs index f3abc18..967a940 100644 --- a/src/client/object.rs +++ b/src/client/object.rs @@ -1,5 +1,5 @@ -use futures_util::{stream, Stream, TryStream}; -use reqwest::StatusCode; +use futures_util::{stream, Stream, StreamExt, TryStream, TryStreamExt}; +use reqwest::{Response, StatusCode}; use crate::{ error::GoogleResponse, @@ -262,20 +262,7 @@ impl<'a> ObjectClient<'a> { } } - /// Download the content of the object with the specified name in the specified bucket. - /// ### Example - /// ```no_run - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> { - /// use cloud_storage::Client; - /// use cloud_storage::Object; - /// - /// let client = Client::default(); - /// let bytes = client.object().download("my_bucket", "path/to/my/file.png").await?; - /// # Ok(()) - /// # } - /// ``` - pub async fn download(&self, bucket: &str, file_name: &str) -> crate::Result> { + async fn download_response(&self, bucket: &str, file_name: &str) -> crate::Result { let url = format!( "{}/b/{}/o/{}?alt=media", crate::BASE_URL, @@ -289,13 +276,68 @@ impl<'a> ObjectClient<'a> { .headers(self.0.get_headers().await?) .send() .await?; + if resp.status() == StatusCode::NOT_FOUND { Err(crate::Error::Other(resp.text().await?)) } else { - Ok(resp.error_for_status()?.bytes().await?.to_vec()) + Ok(resp.error_for_status()?) } } + /// Download the content of the object with the specified name in the specified bucket. + /// ### Example + /// ```no_run + /// # #[tokio::main] + /// # async fn main() -> Result<(), Box> { + /// use cloud_storage::Client; + /// use cloud_storage::Object; + /// + /// let client = Client::default(); + /// let bytes = client.object().download("my_bucket", "path/to/my/file.png").await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn download(&self, bucket: &str, file_name: &str) -> crate::Result> { + Ok(self + .download_response(bucket, file_name) + .await? + .bytes() + .await? + .into()) + } + + /// Download the content of the object with the specified name in the specified bucket, without + /// allocating the whole file into a vector. + /// ### Example + /// ```no_run + /// # #[tokio::main] + /// # async fn main() -> Result<(), Box> { + /// use cloud_storage::Client; + /// use cloud_storage::Object; + /// use futures_util::stream::StreamExt; + /// use tokio::fs::File; + /// use tokio::io::{AsyncWriteExt, BufWriter}; + /// + /// let client = Client::default(); + /// let mut stream = client.object().download_bytes_stream("my_bucket", "path/to/my/file.png").await?; + /// let mut file = BufWriter::new(File::create("file.png").await?); + /// while let Some(bytes) = stream.next().await { + /// file.write_all(&bytes?).await?; + /// } + /// file.flush().await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn download_bytes_stream( + &self, + bucket: &str, + file_name: &str, + ) -> crate::Result> + Unpin> { + let response = self.download_response(bucket, file_name).await?; + let bytes = response.bytes_stream().map_err(crate::Error::from); + Ok(bytes) + } + /// Download the content of the object with the specified name in the specified bucket, without /// allocating the whole file into a vector. /// ### Example @@ -318,26 +360,13 @@ impl<'a> ObjectClient<'a> { /// # Ok(()) /// # } /// ``` + #[deprecated = "Streaming u8 is inefficient, use download_bytes_stream instead"] pub async fn download_streamed( &self, bucket: &str, file_name: &str, ) -> crate::Result> + Unpin> { - use futures_util::{StreamExt, TryStreamExt}; - let url = format!( - "{}/b/{}/o/{}?alt=media", - crate::BASE_URL, - percent_encode(bucket), - percent_encode(file_name), - ); - let response = self - .0 - .client - .get(&url) - .headers(self.0.get_headers().await?) - .send() - .await? - .error_for_status()?; + let response = self.download_response(bucket, file_name).await?; let size = response.content_length(); let bytes = response .bytes_stream() diff --git a/src/lib.rs b/src/lib.rs index 4600512..7953ef9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,9 @@ mod error; mod resources; mod token; +/// re-exported type that appears in public api +pub use bytes::Bytes; + use crate::resources::service_account::ServiceAccount; pub use crate::{ client::Client, diff --git a/src/resources/object.rs b/src/resources/object.rs index 0fd7283..63ef2d2 100644 --- a/src/resources/object.rs +++ b/src/resources/object.rs @@ -449,16 +449,48 @@ impl Object { /// # } /// ``` #[cfg(feature = "global-client")] + #[deprecated = "Streaming u8 is inefficient, use download_bytes_stream instead"] pub async fn download_streamed( bucket: &str, file_name: &str, ) -> crate::Result> + Unpin> { + #[allow(deprecated)] // this calling function is deprecated as well crate::CLOUD_CLIENT .object() .download_streamed(bucket, file_name) .await } + /// Download the content of the object with the specified name in the specified bucket, without + /// allocating the whole file into a vector. + /// ### Example + /// ```no_run + /// # #[tokio::main] + /// # async fn main() -> Result<(), Box> { + /// use cloud_storage::Object; + /// use futures_util::stream::StreamExt; + /// use std::fs::File; + /// use std::io::{BufWriter, Write}; + /// + /// let mut stream = Object::download_bytes_stream("my_bucket", "path/to/my/file.png").await?; + /// let mut file = BufWriter::new(File::create("file.png")?); + /// while let Some(bytes) = stream.next().await { + /// file.write_all(&bytes?)?; + /// } + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "global-client")] + pub async fn download_bytes_stream( + bucket: &str, + file_name: &str, + ) -> crate::Result> + Unpin> { + crate::CLOUD_CLIENT + .object() + .download_bytes_stream(bucket, file_name) + .await + } + /// Obtains a single object with the specified name in the specified bucket. /// ### Example /// ```no_run