Skip to content

Commit 7d4a43d

Browse files
committed
refactor(download): remove duplication in retry logic
Signed-off-by: Marcel Guzik <[email protected]>
1 parent b78bd41 commit 7d4a43d

File tree

1 file changed

+37
-53
lines changed

1 file changed

+37
-53
lines changed

crates/common/download/src/download.rs

Lines changed: 37 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
mod partial_response;
1+
pub mod partial_response;
22
use crate::error::DownloadError;
33
use crate::error::ErrContext;
44
use anyhow::anyhow;
@@ -10,10 +10,10 @@ use log::info;
1010
use log::warn;
1111
use nix::sys::statvfs;
1212
pub use partial_response::InvalidResponseError;
13-
use reqwest::header;
1413
use reqwest::header::HeaderMap;
1514
use reqwest::Client;
1615
use reqwest::Identity;
16+
use reqwest::Response;
1717
use serde::Deserialize;
1818
use serde::Serialize;
1919
use std::fs;
@@ -160,14 +160,8 @@ impl Downloader {
160160
SaveChunksError::Network(err) => {
161161
warn!("Error while downloading response: {err}.\nRetrying...");
162162

163-
match response.headers().get(header::ACCEPT_RANGES) {
164-
Some(unit) if unit == "bytes" => {
165-
self.download_remaining(url, file.as_file_mut()).await?;
166-
}
167-
_ => {
168-
self.retry(url, file.as_file_mut()).await?;
169-
}
170-
}
163+
self.download_continue(url, file.as_file_mut(), response)
164+
.await?;
171165
}
172166
SaveChunksError::Io(err) => {
173167
return Err(DownloadError::FromIo {
@@ -191,26 +185,24 @@ impl Downloader {
191185
Ok(())
192186
}
193187

194-
/// Retries the download requesting only the remaining file part.
188+
/// If interrupted, continues ongoing download.
195189
///
196-
/// If the server does support it, a range request is used to download only
197-
/// the remaining range of the file. If the range request could not be used,
198-
/// [`retry`](Downloader::retry) is used instead.
199-
async fn download_remaining(
190+
/// If the server supports is, a range request is used to download only the
191+
/// remaining range of the file. Otherwise, progress is restarted and we
192+
/// download full range of the file again.
193+
async fn download_continue(
200194
&self,
201195
url: &DownloadInfo,
202196
file: &mut File,
197+
mut prev_response: Response,
203198
) -> Result<(), DownloadError> {
204199
loop {
205-
let file_pos = file
206-
.stream_position()
207-
.context("Can't get file cursor position".to_string())?;
208-
209-
let mut response = self.request_range_from(url, file_pos).await?;
200+
let offset = next_request_offset(&prev_response, file);
201+
let mut response = self.request_range_from(url, offset).await?;
210202
let offset = partial_response::response_range_start(&response)?;
211203

212204
if offset != 0 {
213-
info!("Resuming file download at position={file_pos}");
205+
info!("Resuming file download at position={offset}");
214206
} else {
215207
info!("Could not resume download, restarting");
216208
}
@@ -219,36 +211,7 @@ impl Downloader {
219211
Ok(()) => break,
220212

221213
Err(SaveChunksError::Network(err)) => {
222-
warn!("Error while downloading response: {err}.\nRetrying...");
223-
continue;
224-
}
225-
226-
Err(SaveChunksError::Io(err)) => {
227-
return Err(DownloadError::FromIo {
228-
source: err,
229-
context: "Error while saving to file".to_string(),
230-
})
231-
}
232-
}
233-
}
234-
235-
Ok(())
236-
}
237-
238-
/// Retries downloading the file.
239-
///
240-
/// Retries initial request and downloads the entire file once again. If
241-
/// upon the initial request server signaled support for range requests,
242-
/// [`download_remaining`](Downloader::download_remaining) is used instead.
243-
async fn retry(&self, url: &DownloadInfo, file: &mut File) -> Result<(), DownloadError> {
244-
loop {
245-
info!("Could not resume download, restarting");
246-
let mut response = self.request_range_from(url, 0).await?;
247-
248-
match save_chunks_to_file_at(&mut response, file, 0).await {
249-
Ok(()) => break,
250-
251-
Err(SaveChunksError::Network(err)) => {
214+
prev_response = response;
252215
warn!("Error while downloading response: {err}.\nRetrying...");
253216
continue;
254217
}
@@ -359,7 +322,7 @@ impl Downloader {
359322
/// We use a half-open range with only a lower bound, because we expect to use
360323
/// it to download static resources which do not change, and only as a recovery
361324
/// mechanism in case of network failures.
362-
async fn request_range_from(
325+
pub async fn request_range_from(
363326
&self,
364327
url: &DownloadInfo,
365328
range_start: u64,
@@ -427,7 +390,11 @@ enum SaveChunksError {
427390
}
428391

429392
#[allow(clippy::unnecessary_cast)]
430-
fn try_pre_allocate_space(file: &File, path: &Path, file_len: u64) -> Result<(), DownloadError> {
393+
pub fn try_pre_allocate_space(
394+
file: &File,
395+
path: &Path,
396+
file_len: u64,
397+
) -> Result<(), DownloadError> {
431398
if file_len == 0 {
432399
return Ok(());
433400
}
@@ -457,5 +424,22 @@ fn try_pre_allocate_space(file: &File, path: &Path, file_len: u64) -> Result<(),
457424
Ok(())
458425
}
459426

427+
fn next_request_offset(prev_response: &Response, file: &mut File) -> u64 {
428+
use hyper::header;
429+
use hyper::StatusCode;
430+
use std::io::Seek;
431+
let pos = file.stream_position().unwrap();
432+
if prev_response.status() == StatusCode::PARTIAL_CONTENT
433+
|| prev_response
434+
.headers()
435+
.get(header::ACCEPT_RANGES)
436+
.is_some_and(|unit| unit == "bytes")
437+
{
438+
pos
439+
} else {
440+
0
441+
}
442+
}
443+
460444
#[cfg(test)]
461445
mod tests;

0 commit comments

Comments
 (0)