fast_down/http/
prefetch.rs1use 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}