Skip to content

Commit d06af1b

Browse files
djcrami3l
authored andcommitted
download: attach download functions to Backend type
1 parent 028ae43 commit d06af1b

File tree

4 files changed

+182
-183
lines changed

4 files changed

+182
-183
lines changed

download/src/lib.rs

Lines changed: 122 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -30,143 +30,146 @@ pub enum Backend {
3030
Reqwest(TlsBackend),
3131
}
3232

33-
#[derive(Debug, Copy, Clone)]
34-
pub enum TlsBackend {
35-
Rustls,
36-
NativeTls,
37-
}
38-
39-
#[derive(Debug, Copy, Clone)]
40-
pub enum Event<'a> {
41-
ResumingPartialDownload,
42-
/// Received the Content-Length of the to-be downloaded data.
43-
DownloadContentLengthReceived(u64),
44-
/// Received some data.
45-
DownloadDataReceived(&'a [u8]),
46-
}
47-
48-
type DownloadCallback<'a> = &'a dyn Fn(Event<'_>) -> Result<()>;
33+
impl Backend {
34+
pub async fn download_to_path(
35+
self,
36+
url: &Url,
37+
path: &Path,
38+
resume_from_partial: bool,
39+
callback: Option<DownloadCallback<'_>>,
40+
) -> Result<()> {
41+
let Err(err) = self
42+
.download_impl(url, path, resume_from_partial, callback)
43+
.await
44+
else {
45+
return Ok(());
46+
};
4947

50-
async fn download_with_backend(
51-
backend: Backend,
52-
url: &Url,
53-
resume_from: u64,
54-
callback: DownloadCallback<'_>,
55-
) -> Result<()> {
56-
match backend {
57-
Backend::Curl => curl::download(url, resume_from, callback),
58-
Backend::Reqwest(tls) => reqwest_be::download(url, resume_from, callback, tls).await,
48+
// TODO: We currently clear up the cached download on any error, should we restrict it to a subset?
49+
Err(
50+
if let Err(file_err) = remove_file(path).context("cleaning up cached downloads") {
51+
file_err.context(err)
52+
} else {
53+
err
54+
},
55+
)
5956
}
60-
}
61-
62-
pub async fn download_to_path_with_backend(
63-
backend: Backend,
64-
url: &Url,
65-
path: &Path,
66-
resume_from_partial: bool,
67-
callback: Option<DownloadCallback<'_>>,
68-
) -> Result<()> {
69-
let Err(err) =
70-
download_to_path_with_backend_(backend, url, path, resume_from_partial, callback).await
71-
else {
72-
return Ok(());
73-
};
74-
75-
// TODO: We currently clear up the cached download on any error, should we restrict it to a subset?
76-
Err(
77-
if let Err(file_err) = remove_file(path).context("cleaning up cached downloads") {
78-
file_err.context(err)
79-
} else {
80-
err
81-
},
82-
)
83-
}
8457

85-
pub async fn download_to_path_with_backend_(
86-
backend: Backend,
87-
url: &Url,
88-
path: &Path,
89-
resume_from_partial: bool,
90-
callback: Option<DownloadCallback<'_>>,
91-
) -> Result<()> {
92-
use std::cell::RefCell;
93-
use std::fs::OpenOptions;
94-
use std::io::{Read, Seek, SeekFrom, Write};
95-
96-
let (file, resume_from) = if resume_from_partial {
97-
// TODO: blocking call
98-
let possible_partial = OpenOptions::new().read(true).open(path);
99-
100-
let downloaded_so_far = if let Ok(mut partial) = possible_partial {
101-
if let Some(cb) = callback {
102-
cb(Event::ResumingPartialDownload)?;
103-
104-
let mut buf = vec![0; 32768];
105-
let mut downloaded_so_far = 0;
106-
loop {
107-
let n = partial.read(&mut buf)?;
108-
downloaded_so_far += n as u64;
109-
if n == 0 {
110-
break;
58+
async fn download_impl(
59+
self,
60+
url: &Url,
61+
path: &Path,
62+
resume_from_partial: bool,
63+
callback: Option<DownloadCallback<'_>>,
64+
) -> Result<()> {
65+
use std::cell::RefCell;
66+
use std::fs::OpenOptions;
67+
use std::io::{Read, Seek, SeekFrom, Write};
68+
69+
let (file, resume_from) = if resume_from_partial {
70+
// TODO: blocking call
71+
let possible_partial = OpenOptions::new().read(true).open(path);
72+
73+
let downloaded_so_far = if let Ok(mut partial) = possible_partial {
74+
if let Some(cb) = callback {
75+
cb(Event::ResumingPartialDownload)?;
76+
77+
let mut buf = vec![0; 32768];
78+
let mut downloaded_so_far = 0;
79+
loop {
80+
let n = partial.read(&mut buf)?;
81+
downloaded_so_far += n as u64;
82+
if n == 0 {
83+
break;
84+
}
85+
cb(Event::DownloadDataReceived(&buf[..n]))?;
11186
}
112-
cb(Event::DownloadDataReceived(&buf[..n]))?;
113-
}
11487

115-
downloaded_so_far
88+
downloaded_so_far
89+
} else {
90+
let file_info = partial.metadata()?;
91+
file_info.len()
92+
}
11693
} else {
117-
let file_info = partial.metadata()?;
118-
file_info.len()
119-
}
94+
0
95+
};
96+
97+
// TODO: blocking call
98+
let mut possible_partial = OpenOptions::new()
99+
.write(true)
100+
.create(true)
101+
.truncate(false)
102+
.open(path)
103+
.context("error opening file for download")?;
104+
105+
possible_partial.seek(SeekFrom::End(0))?;
106+
107+
(possible_partial, downloaded_so_far)
120108
} else {
121-
0
109+
(
110+
OpenOptions::new()
111+
.write(true)
112+
.create(true)
113+
.truncate(true)
114+
.open(path)
115+
.context("error creating file for download")?,
116+
0,
117+
)
122118
};
123119

124-
// TODO: blocking call
125-
let mut possible_partial = OpenOptions::new()
126-
.write(true)
127-
.create(true)
128-
.truncate(false)
129-
.open(path)
130-
.context("error opening file for download")?;
120+
let file = RefCell::new(file);
131121

132-
possible_partial.seek(SeekFrom::End(0))?;
122+
// TODO: the sync callback will stall the async runtime if IO calls block, which is OS dependent. Rearrange.
123+
self.download(url, resume_from, &|event| {
124+
if let Event::DownloadDataReceived(data) = event {
125+
file.borrow_mut()
126+
.write_all(data)
127+
.context("unable to write download to disk")?;
128+
}
129+
match callback {
130+
Some(cb) => cb(event),
131+
None => Ok(()),
132+
}
133+
})
134+
.await?;
133135

134-
(possible_partial, downloaded_so_far)
135-
} else {
136-
(
137-
OpenOptions::new()
138-
.write(true)
139-
.create(true)
140-
.truncate(true)
141-
.open(path)
142-
.context("error creating file for download")?,
143-
0,
144-
)
145-
};
136+
file.borrow_mut()
137+
.sync_data()
138+
.context("unable to sync download to disk")?;
146139

147-
let file = RefCell::new(file);
140+
Ok::<(), anyhow::Error>(())
141+
}
148142

149-
// TODO: the sync callback will stall the async runtime if IO calls block, which is OS dependent. Rearrange.
150-
download_with_backend(backend, url, resume_from, &|event| {
151-
if let Event::DownloadDataReceived(data) = event {
152-
file.borrow_mut()
153-
.write_all(data)
154-
.context("unable to write download to disk")?;
155-
}
156-
match callback {
157-
Some(cb) => cb(event),
158-
None => Ok(()),
143+
async fn download(
144+
self,
145+
url: &Url,
146+
resume_from: u64,
147+
callback: DownloadCallback<'_>,
148+
) -> Result<()> {
149+
match self {
150+
Self::Curl => curl::download(url, resume_from, callback),
151+
Self::Reqwest(tls) => reqwest_be::download(url, resume_from, callback, tls).await,
159152
}
160-
})
161-
.await?;
153+
}
154+
}
162155

163-
file.borrow_mut()
164-
.sync_data()
165-
.context("unable to sync download to disk")?;
156+
#[derive(Debug, Copy, Clone)]
157+
pub enum TlsBackend {
158+
Rustls,
159+
NativeTls,
160+
}
166161

167-
Ok::<(), anyhow::Error>(())
162+
#[derive(Debug, Copy, Clone)]
163+
pub enum Event<'a> {
164+
ResumingPartialDownload,
165+
/// Received the Content-Length of the to-be downloaded data.
166+
DownloadContentLengthReceived(u64),
167+
/// Received some data.
168+
DownloadDataReceived(&'a [u8]),
168169
}
169170

171+
type DownloadCallback<'a> = &'a dyn Fn(Event<'_>) -> Result<()>;
172+
170173
#[cfg(all(
171174
not(feature = "reqwest-rustls-tls"),
172175
not(feature = "reqwest-native-tls"),

download/tests/download-curl-resume.rs

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ async fn partially_downloaded_file_gets_resumed_from_byte_offset() {
2020
write_file(&target_path, "123");
2121

2222
let from_url = Url::from_file_path(&from_path).unwrap();
23-
download_to_path_with_backend(Backend::Curl, &from_url, &target_path, true, None)
23+
Backend::Curl
24+
.download_to_path(&from_url, &target_path, true, None)
2425
.await
2526
.expect("Test download failed");
2627

@@ -41,34 +42,34 @@ async fn callback_gets_all_data_as_if_the_download_happened_all_at_once() {
4142
let callback_len = Mutex::new(None);
4243
let received_in_callback = Mutex::new(Vec::new());
4344

44-
download_to_path_with_backend(
45-
Backend::Curl,
46-
&from_url,
47-
&target_path,
48-
true,
49-
Some(&|msg| {
50-
match msg {
51-
Event::ResumingPartialDownload => {
52-
assert!(!callback_partial.load(Ordering::SeqCst));
53-
callback_partial.store(true, Ordering::SeqCst);
54-
}
55-
Event::DownloadContentLengthReceived(len) => {
56-
let mut flag = callback_len.lock().unwrap();
57-
assert!(flag.is_none());
58-
*flag = Some(len);
59-
}
60-
Event::DownloadDataReceived(data) => {
61-
for b in data.iter() {
62-
received_in_callback.lock().unwrap().push(*b);
45+
Backend::Curl
46+
.download_to_path(
47+
&from_url,
48+
&target_path,
49+
true,
50+
Some(&|msg| {
51+
match msg {
52+
Event::ResumingPartialDownload => {
53+
assert!(!callback_partial.load(Ordering::SeqCst));
54+
callback_partial.store(true, Ordering::SeqCst);
55+
}
56+
Event::DownloadContentLengthReceived(len) => {
57+
let mut flag = callback_len.lock().unwrap();
58+
assert!(flag.is_none());
59+
*flag = Some(len);
60+
}
61+
Event::DownloadDataReceived(data) => {
62+
for b in data.iter() {
63+
received_in_callback.lock().unwrap().push(*b);
64+
}
6365
}
6466
}
65-
}
6667

67-
Ok(())
68-
}),
69-
)
70-
.await
71-
.expect("Test download failed");
68+
Ok(())
69+
}),
70+
)
71+
.await
72+
.expect("Test download failed");
7273

7374
assert!(callback_partial.into_inner());
7475
assert_eq!(*callback_len.lock().unwrap(), Some(5));

0 commit comments

Comments
 (0)