fast_down/http/
prefetch.rs

1use crate::{
2    UrlInfo,
3    http::{GetResponse, HttpClient, HttpError, HttpHeaders, HttpRequestBuilder, HttpResponse},
4};
5use content_disposition;
6use std::future::Future;
7use url::Url;
8
9pub trait Prefetch<Client: HttpClient> {
10    type Error;
11    fn prefetch(
12        &self,
13        url: Url,
14    ) -> impl Future<Output = Result<(UrlInfo, GetResponse<Client>), Self::Error>> + Send;
15}
16
17impl<Client: HttpClient> Prefetch<Client> for Client {
18    type Error = HttpError<Client>;
19    async fn prefetch(&self, url: Url) -> Result<(UrlInfo, GetResponse<Client>), Self::Error> {
20        prefetch(self, url).await
21    }
22}
23
24fn get_filename(headers: &impl HttpHeaders, url: &Url) -> String {
25    headers
26        .get("content-disposition")
27        .ok()
28        .and_then(|s| content_disposition::parse_content_disposition(s).filename_full())
29        .map(|s| urlencoding::decode(&s).map(String::from).unwrap_or(s))
30        .filter(|s| !s.trim().is_empty())
31        .or_else(|| {
32            url.path_segments()
33                .and_then(|mut segments| segments.next_back())
34                .map(|s| urlencoding::decode(s).unwrap_or(s.into()))
35                .filter(|s| !s.trim().is_empty())
36                .map(|s| s.to_string())
37        })
38        .unwrap_or_else(|| url.to_string())
39}
40
41async fn prefetch<Client: HttpClient>(
42    client: &Client,
43    url: Url,
44) -> Result<(UrlInfo, GetResponse<Client>), HttpError<Client>> {
45    let resp = client
46        .get(url, None)
47        .send()
48        .await
49        .map_err(HttpError::Request)?;
50    let headers = resp.headers();
51    let supports_range = headers
52        .get("accept-ranges")
53        .map(|v| v == "bytes")
54        .unwrap_or(false);
55    let size = headers
56        .get("content-length")
57        .ok()
58        .and_then(|v| v.parse().ok())
59        .unwrap_or(0);
60    let final_url = resp.url();
61    Ok((
62        UrlInfo {
63            final_url: final_url.clone(),
64            name: get_filename(headers, final_url),
65            size,
66            supports_range,
67            fast_download: size > 0 && supports_range,
68            etag: headers.get("etag").ok().map(|s| s.to_string()),
69            last_modified: headers.get("last-modified").ok().map(|s| s.to_string()),
70        },
71        resp,
72    ))
73}