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