• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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