1- mod partial_response;
1+ pub mod partial_response;
22use crate :: error:: DownloadError ;
33use crate :: error:: ErrContext ;
44use anyhow:: anyhow;
@@ -10,10 +10,10 @@ use log::info;
1010use log:: warn;
1111use nix:: sys:: statvfs;
1212pub use partial_response:: InvalidResponseError ;
13- use reqwest:: header;
1413use reqwest:: header:: HeaderMap ;
1514use reqwest:: Client ;
1615use reqwest:: Identity ;
16+ use reqwest:: Response ;
1717use serde:: Deserialize ;
1818use serde:: Serialize ;
1919use std:: fs;
@@ -160,14 +160,8 @@ impl Downloader {
160160 SaveChunksError :: Network ( err) => {
161161 warn ! ( "Error while downloading response: {err}.\n Retrying..." ) ;
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}.\n Retrying..." ) ;
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}.\n Retrying..." ) ;
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) ]
461445mod tests;
0 commit comments