1 // Copyright (c) 2023 Huawei Device Co., Ltd. 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 mod builder; 15 mod operator; 16 17 use std::time::Instant; 18 19 pub use builder::DownloaderBuilder; 20 use builder::WantsBody; 21 use operator::Console; 22 pub use operator::{DownloadFuture, DownloadOperator, ProgressFuture}; 23 use ylong_http::body::async_impl::Body; 24 25 // TODO: Adapter, use Response<HttpBody> later. 26 use crate::async_impl::Response; 27 use crate::{ErrorKind, HttpClientError, SpeedLimit, Timeout}; 28 29 /// A downloader that can help you download the response body. 30 /// 31 /// A `Downloader` provides a template method for downloading the body and 32 /// needs to use a structure that implements [`DownloadOperator`] trait to read 33 /// the body. 34 /// 35 /// The `DownloadOperator` trait provides two kinds of methods - [`download`] 36 /// and [`progress`], where: 37 /// 38 /// - `download` methods are responsible for reading and copying the body to 39 /// certain places. 40 /// 41 /// - `progress` methods are responsible for progress display. 42 /// 43 /// You only need to provide a structure that implements the `DownloadOperator` 44 /// trait to complete the download process. 45 /// 46 /// A default structure `Console` which implements `DownloadOperator` is 47 /// provided to show download message on console. You can use 48 /// `Downloader::console` to build a `Downloader` which based on it. 49 /// 50 /// [`DownloadOperator`]: DownloadOperator 51 /// [`download`]: DownloadOperator::download 52 /// [`progress`]: DownloadOperator::progress 53 /// 54 /// # Examples 55 /// 56 /// `Console`: 57 /// ```no_run 58 /// # use ylong_http_client::async_impl::{Downloader, HttpBody, Response}; 59 /// 60 /// # async fn download_and_show_progress_on_console(response: Response) { 61 /// // Creates a default `Downloader` that show progress on console. 62 /// let mut downloader = Downloader::console(response); 63 /// let _ = downloader.download().await; 64 /// # } 65 /// ``` 66 /// 67 /// `Custom`: 68 /// ```no_run 69 /// # use std::pin::Pin; 70 /// # use std::task::{Context, Poll}; 71 /// # use ylong_http_client::async_impl::{Downloader, DownloadOperator, HttpBody, Response}; 72 /// # use ylong_http_client::{HttpClientError, SpeedLimit, Timeout}; 73 /// 74 /// # async fn download_and_show_progress(response: Response) { 75 /// // Customizes your own `DownloadOperator`. 76 /// struct MyDownloadOperator; 77 /// 78 /// impl DownloadOperator for MyDownloadOperator { 79 /// fn poll_download( 80 /// self: Pin<&mut Self>, 81 /// cx: &mut Context<'_>, 82 /// data: &[u8], 83 /// ) -> Poll<Result<usize, HttpClientError>> { 84 /// todo!() 85 /// } 86 /// 87 /// fn poll_progress( 88 /// self: Pin<&mut Self>, 89 /// cx: &mut Context<'_>, 90 /// downloaded: u64, 91 /// total: Option<u64>, 92 /// ) -> Poll<Result<(), HttpClientError>> { 93 /// // Writes your customize method. 94 /// todo!() 95 /// } 96 /// } 97 /// 98 /// // Creates a default `Downloader` based on `MyDownloadOperator`. 99 /// // Configures your downloader by using `DownloaderBuilder`. 100 /// let mut downloader = Downloader::builder() 101 /// .body(response) 102 /// .operator(MyDownloadOperator) 103 /// .timeout(Timeout::none()) 104 /// .speed_limit(SpeedLimit::none()) 105 /// .build(); 106 /// let _ = downloader.download().await; 107 /// # } 108 /// ``` 109 pub struct Downloader<T> { 110 operator: T, 111 body: Response, 112 config: DownloadConfig, 113 info: Option<DownloadInfo>, 114 } 115 116 impl Downloader<()> { 117 /// Creates a `Downloader` that based on a default `DownloadOperator` which 118 /// show progress on console. 119 /// 120 /// # Examples 121 /// 122 /// ```no_run 123 /// # use ylong_http_client::async_impl::{Downloader, HttpBody, Response}; 124 /// 125 /// # async fn download_and_show_progress_on_console(response: Response) { 126 /// // Creates a default `Downloader` that show progress on console. 127 /// let mut downloader = Downloader::console(response); 128 /// let _ = downloader.download().await; 129 /// # } 130 /// ``` console(response: Response) -> Downloader<Console>131 pub fn console(response: Response) -> Downloader<Console> { 132 Self::builder().body(response).console().build() 133 } 134 135 /// Creates a `DownloaderBuilder` and configures downloader step by step. 136 /// 137 /// # Examples 138 /// 139 /// ``` 140 /// # use ylong_http_client::async_impl::Downloader; 141 /// 142 /// let builder = Downloader::builder(); 143 /// ``` builder() -> DownloaderBuilder<WantsBody>144 pub fn builder() -> DownloaderBuilder<WantsBody> { 145 DownloaderBuilder::new() 146 } 147 } 148 149 impl<T: DownloadOperator + Unpin> Downloader<T> { 150 /// Starts downloading that uses this `Downloader`'s configurations. 151 /// 152 /// The download and progress methods of the `DownloadOperator` will be 153 /// called multiple times until the download is complete. 154 /// 155 /// # Examples 156 /// 157 /// ``` 158 /// # use ylong_http_client::async_impl::{Downloader, HttpBody, Response}; 159 /// 160 /// # async fn download_response_body(response: Response) { 161 /// let mut downloader = Downloader::console(response); 162 /// let _result = downloader.download().await; 163 /// # } 164 /// ``` download(&mut self) -> Result<(), HttpClientError>165 pub async fn download(&mut self) -> Result<(), HttpClientError> { 166 // Construct new download info, or reuse previous info. 167 if self.info.is_none() { 168 let content_length = self 169 .body 170 .headers() 171 .get("Content") 172 .and_then(|v| v.to_str().ok()) 173 .and_then(|v| v.parse::<u64>().ok()); 174 self.info = Some(DownloadInfo::new(content_length)); 175 } 176 self.limited_download().await 177 } 178 179 // Downloads response body with speed limitation. 180 // TODO: Speed Limit. limited_download(&mut self) -> Result<(), HttpClientError>181 async fn limited_download(&mut self) -> Result<(), HttpClientError> { 182 self.show_progress().await?; 183 self.check_timeout()?; 184 185 let mut buf = [0; 16 * 1024]; 186 187 loop { 188 let data_size = match self.body.body_mut().data(&mut buf).await { 189 Ok(0) => { 190 self.show_progress().await?; 191 return Ok(()); 192 } 193 Ok(size) => size, 194 Err(e) => { 195 return Err(HttpClientError::new_with_cause( 196 ErrorKind::BodyTransfer, 197 Some(e), 198 )) 199 } 200 }; 201 202 let data = &buf[..data_size]; 203 let mut size = 0; 204 while size != data.len() { 205 self.check_timeout()?; 206 size += self.operator.download(&data[size..]).await?; 207 self.info.as_mut().unwrap().downloaded_bytes += data.len() as u64; 208 self.show_progress().await?; 209 } 210 } 211 } 212 check_timeout(&mut self) -> Result<(), HttpClientError>213 fn check_timeout(&mut self) -> Result<(), HttpClientError> { 214 if let Some(timeout) = self.config.timeout.inner() { 215 let now = Instant::now(); 216 if now.duration_since(self.info.as_mut().unwrap().start_time) >= timeout { 217 return Err(HttpClientError::new_with_message( 218 ErrorKind::Timeout, 219 "Download timeout", 220 )); 221 } 222 } 223 Ok(()) 224 } 225 show_progress(&mut self) -> Result<(), HttpClientError>226 async fn show_progress(&mut self) -> Result<(), HttpClientError> { 227 let info = self.info.as_mut().unwrap(); 228 self.operator 229 .progress(info.downloaded_bytes, info.total_bytes) 230 .await 231 } 232 } 233 234 struct DownloadInfo { 235 pub(crate) start_time: Instant, 236 pub(crate) downloaded_bytes: u64, 237 pub(crate) total_bytes: Option<u64>, 238 } 239 240 impl DownloadInfo { new(total_bytes: Option<u64>) -> Self241 fn new(total_bytes: Option<u64>) -> Self { 242 Self { 243 start_time: Instant::now(), 244 downloaded_bytes: 0, 245 total_bytes, 246 } 247 } 248 } 249 250 struct DownloadConfig { 251 pub(crate) timeout: Timeout, 252 pub(crate) speed_limit: SpeedLimit, 253 } 254 255 impl Default for DownloadConfig { default() -> Self256 fn default() -> Self { 257 Self { 258 timeout: Timeout::none(), 259 speed_limit: SpeedLimit::none(), 260 } 261 } 262 } 263