1use std::collections::VecDeque;
9use std::fmt::Debug;
10use std::io::{self, ErrorKind, Read};
11use std::path::PathBuf;
12use std::sync::Arc;
13use std::time::{Duration, Instant};
14
15use ecow::EcoString;
16use native_tls::{Certificate, TlsConnector};
17use once_cell::sync::OnceCell;
18use ureq::Response;
19
20pub trait Progress {
22 fn print_start(&mut self);
24
25 fn print_progress(&mut self, state: &DownloadState);
27
28 fn print_finish(&mut self, state: &DownloadState);
30}
31
32pub struct ProgressSink;
35
36impl Progress for ProgressSink {
37 fn print_start(&mut self) {}
38 fn print_progress(&mut self, _: &DownloadState) {}
39 fn print_finish(&mut self, _: &DownloadState) {}
40}
41
42#[derive(Debug)]
44pub struct DownloadState {
45 pub content_len: Option<usize>,
48 pub total_downloaded: usize,
50 pub bytes_per_second: VecDeque<usize>,
52 pub start_time: Instant,
54}
55
56pub struct Downloader {
58 user_agent: EcoString,
59 cert_path: Option<PathBuf>,
60 cert: OnceCell<Certificate>,
61}
62
63impl Downloader {
64 pub fn new(user_agent: impl Into<EcoString>) -> Self {
66 Self {
67 user_agent: user_agent.into(),
68 cert_path: None,
69 cert: OnceCell::new(),
70 }
71 }
72
73 pub fn with_path(user_agent: impl Into<EcoString>, cert_path: PathBuf) -> Self {
77 Self {
78 user_agent: user_agent.into(),
79 cert_path: Some(cert_path),
80 cert: OnceCell::new(),
81 }
82 }
83
84 pub fn with_cert(user_agent: impl Into<EcoString>, cert: Certificate) -> Self {
86 Self {
87 user_agent: user_agent.into(),
88 cert_path: None,
89 cert: OnceCell::with_value(cert),
90 }
91 }
92
93 pub fn cert(&self) -> Option<io::Result<&Certificate>> {
100 self.cert_path.as_ref().map(|path| {
101 self.cert.get_or_try_init(|| {
102 let pem = std::fs::read(path)?;
103 Certificate::from_pem(&pem).map_err(io::Error::other)
104 })
105 })
106 }
107
108 #[allow(clippy::result_large_err)]
110 pub fn download(&self, url: &str) -> Result<ureq::Response, ureq::Error> {
111 let mut builder = ureq::AgentBuilder::new();
112 let mut tls = TlsConnector::builder();
113
114 builder = builder.user_agent(&self.user_agent);
116
117 if let Some(proxy) = env_proxy::for_url_str(url)
119 .to_url()
120 .and_then(|url| ureq::Proxy::new(url).ok())
121 {
122 builder = builder.proxy(proxy);
123 }
124
125 if let Some(cert) = self.cert() {
127 tls.add_root_certificate(cert?.clone());
128 }
129
130 let connector =
132 tls.build().map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
133 builder = builder.tls_connector(Arc::new(connector));
134
135 builder.build().get(url).call()
136 }
137
138 #[allow(clippy::result_large_err)]
140 pub fn download_with_progress(
141 &self,
142 url: &str,
143 progress: &mut dyn Progress,
144 ) -> Result<Vec<u8>, ureq::Error> {
145 progress.print_start();
146 let response = self.download(url)?;
147 Ok(RemoteReader::from_response(response, progress).download()?)
148 }
149}
150
151impl Debug for Downloader {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 f.debug_struct("Downloader")
154 .field("user_agent", &self.user_agent)
155 .field("cert_path", &self.cert_path)
156 .field(
157 "cert",
158 &self
159 .cert
160 .get()
161 .map(|_| typst_utils::debug(|f| write!(f, "Certificate(..)"))),
162 )
163 .finish()
164 }
165}
166
167const SAMPLES: usize = 5;
169
170struct RemoteReader<'p> {
173 reader: Box<dyn Read + Send + Sync + 'static>,
175 state: DownloadState,
177 last_progress: Option<Instant>,
179 progress: &'p mut dyn Progress,
181}
182
183impl<'p> RemoteReader<'p> {
184 fn from_response(response: Response, progress: &'p mut dyn Progress) -> Self {
189 let content_len: Option<usize> = response
190 .header("Content-Length")
191 .and_then(|header| header.parse().ok());
192
193 Self {
194 reader: response.into_reader(),
195 last_progress: None,
196 state: DownloadState {
197 content_len,
198 total_downloaded: 0,
199 bytes_per_second: VecDeque::with_capacity(SAMPLES),
200 start_time: Instant::now(),
201 },
202 progress,
203 }
204 }
205
206 fn download(mut self) -> io::Result<Vec<u8>> {
209 let mut buffer = vec![0; 8192];
210 let mut data = match self.state.content_len {
211 Some(content_len) => Vec::with_capacity(content_len),
212 None => Vec::with_capacity(8192),
213 };
214
215 let mut downloaded_this_sec = 0;
216 loop {
217 let read = match self.reader.read(&mut buffer) {
218 Ok(0) => break,
219 Ok(n) => n,
220 Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
224 Err(e) => return Err(e),
225 };
226
227 data.extend(&buffer[..read]);
228
229 let last_printed = match self.last_progress {
230 Some(prev) => prev,
231 None => {
232 let current_time = Instant::now();
233 self.last_progress = Some(current_time);
234 current_time
235 }
236 };
237 let elapsed = Instant::now().saturating_duration_since(last_printed);
238
239 downloaded_this_sec += read;
240 self.state.total_downloaded += read;
241
242 if elapsed >= Duration::from_secs(1) {
243 if self.state.bytes_per_second.len() == SAMPLES {
244 self.state.bytes_per_second.pop_back();
245 }
246
247 self.state.bytes_per_second.push_front(downloaded_this_sec);
248 downloaded_this_sec = 0;
249
250 self.progress.print_progress(&self.state);
251 self.last_progress = Some(Instant::now());
252 }
253 }
254
255 self.progress.print_finish(&self.state);
256
257 Ok(data)
258 }
259}