Skip to main content

fast_down/http/
prefetch.rs

1use crate::{
2    UrlInfo,
3    http::{GetResponse, HttpClient, HttpError, HttpHeaders, HttpRequestBuilder, HttpResponse},
4    url_info::FileId,
5    utils::ContentDisposition,
6};
7use std::{borrow::Borrow, future::Future, time::Duration};
8use url::Url;
9
10pub type PrefetchResult<Client> =
11    Result<(UrlInfo, GetResponse<Client>), (HttpError<Client>, Option<Duration>)>;
12
13pub trait Prefetch<Client: HttpClient> {
14    fn prefetch(&self, url: Url) -> impl Future<Output = PrefetchResult<Client>> + Send;
15}
16
17impl<Client, BorrowClient> Prefetch<Client> for BorrowClient
18where
19    Client: HttpClient,
20    BorrowClient: Borrow<Client> + Sync,
21{
22    async fn prefetch(&self, url: Url) -> PrefetchResult<Client> {
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| ContentDisposition::parse(s).filename)
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>(client: &Client, url: Url) -> PrefetchResult<Client> {
46    let resp = client
47        .get(url, None)
48        .send()
49        .await
50        .map_err(|(e, d)| (HttpError::Request(e), d))?;
51    let headers = resp.headers();
52    let supports_range = headers
53        .get("accept-ranges")
54        .map(|v| v == "bytes")
55        .unwrap_or(false);
56    let size = headers
57        .get("content-length")
58        .ok()
59        .and_then(|v| v.parse().ok())
60        .unwrap_or(0);
61    let final_url = resp.url();
62    Ok((
63        UrlInfo {
64            final_url: final_url.clone(),
65            raw_name: get_filename(headers, final_url),
66            size,
67            supports_range,
68            fast_download: size > 0 && supports_range,
69            file_id: FileId::new(headers.get("etag").ok(), headers.get("last-modified").ok()),
70        },
71        resp,
72    ))
73}