fast_down/http/
prefetch.rs

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