fast_down/http/
prefetch.rs1use 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> =
10 Result<(UrlInfo, GetResponse<Client>), (HttpError<Client>, Option<Duration>)>;
11
12pub trait Prefetch<Client: HttpClient> {
13 fn prefetch(&self, url: Url) -> impl Future<Output = PrefetchResult<Client>> + Send;
14}
15
16impl<Client, BorrowClient> Prefetch<Client> for BorrowClient
17where
18 Client: HttpClient,
19 BorrowClient: Borrow<Client> + Sync,
20{
21 async fn prefetch(&self, url: Url) -> PrefetchResult<Client> {
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>(client: &Client, url: Url) -> PrefetchResult<Client> {
45 let resp = client
46 .get(url, None)
47 .send()
48 .await
49 .map_err(|(e, d)| (HttpError::Request(e), d))?;
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 raw_name: get_filename(headers, final_url),
65 size,
66 supports_range,
67 fast_download: size > 0 && supports_range,
68 file_id: FileId::new(headers.get("etag").ok(), headers.get("last-modified").ok()),
69 },
70 resp,
71 ))
72}