fast_down/http/
prefetch.rs

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