@@ -33,47 +33,86 @@ const BACKENDS: &'static [Backend] = &[
33
33
Backend :: Rustls
34
34
] ;
35
35
36
- pub fn download ( url : & Url ,
37
- callback : & Fn ( Event ) -> Result < ( ) > )
38
- -> Result < ( ) > {
39
- for & backend in BACKENDS {
40
- match download_with_backend ( backend, url, callback) {
41
- Err ( Error ( ErrorKind :: BackendUnavailable ( _) , _) ) => ( ) ,
42
- Err ( e) => return Err ( e) ,
43
- Ok ( ( ) ) => return Ok ( ( ) ) ,
44
- }
45
- }
46
36
47
- Err ( "no working backends" . into ( ) )
48
- }
49
-
50
- pub fn download_with_backend ( backend : Backend ,
37
+ fn download_with_backend ( backend : Backend ,
51
38
url : & Url ,
39
+ resume_from : u64 ,
52
40
callback : & Fn ( Event ) -> Result < ( ) > )
53
41
-> Result < ( ) > {
54
42
match backend {
55
- Backend :: Curl => curl:: download ( url, callback) ,
43
+ Backend :: Curl => curl:: download ( url, resume_from , callback) ,
56
44
Backend :: Hyper => hyper:: download ( url, callback) ,
57
45
Backend :: Rustls => rustls:: download ( url, callback) ,
58
46
}
59
47
}
60
48
49
+ fn supports_partial_download ( backend : & Backend ) -> bool {
50
+ match backend {
51
+ & Backend :: Curl => true ,
52
+ _ => false
53
+ }
54
+ }
55
+
61
56
pub fn download_to_path_with_backend (
62
57
backend : Backend ,
63
58
url : & Url ,
64
59
path : & Path ,
60
+ resume_from_partial : bool ,
65
61
callback : Option < & Fn ( Event ) -> Result < ( ) > > )
66
62
-> Result < ( ) >
67
63
{
68
64
use std:: cell:: RefCell ;
69
- use std:: fs:: { self , File } ;
70
- use std:: io:: Write ;
65
+ use std:: fs:: { self , File , OpenOptions } ;
66
+ use std:: io:: { Read , Write , Seek , SeekFrom } ;
71
67
72
68
|| -> Result < ( ) > {
73
- let file = RefCell :: new ( try!( File :: create ( & path) . chain_err (
74
- || "error creating file for download" ) ) ) ;
69
+ let ( file, resume_from) = if resume_from_partial && supports_partial_download ( & backend) {
70
+ let mut possible_partial = OpenOptions :: new ( )
71
+ . read ( true )
72
+ . open ( & path) ;
73
+
74
+ let downloaded_so_far = if let Ok ( mut partial) = possible_partial {
75
+ if let Some ( cb) = callback {
76
+ println ! ( "Reading file in {}" , path. display( ) ) ;
77
+ let mut buf = vec ! [ 0 ; 1024 * 1024 * 10 ] ;
78
+ let mut downloaded_so_far = 0 ;
79
+ let mut number_of_reads = 0 ;
80
+ loop {
81
+ let n = try!( partial. read ( & mut buf) ) ;
82
+ downloaded_so_far += n as u64 ;
83
+ number_of_reads += 1 ;
84
+ if n == 0 {
85
+ println ! ( "nothing read after {} reads (accumulated {})" , number_of_reads, downloaded_so_far) ;
86
+ break ;
87
+ }
88
+ try!( cb ( Event :: DownloadDataReceived ( & buf[ ..n] ) ) ) ;
89
+ }
90
+ downloaded_so_far
91
+ } else {
92
+ use std:: fs:: Metadata ;
93
+ let file_info = try!( partial. metadata ( ) ) ;
94
+ file_info. len ( )
95
+ }
96
+ } else {
97
+ 0
98
+ } ;
99
+
100
+ let mut possible_partial = try!( OpenOptions :: new ( ) . write ( true ) . create ( true ) . open ( & path) . chain_err ( || "error opening file for download" ) ) ;
101
+ try!( possible_partial. seek ( SeekFrom :: End ( 0 ) ) ) ;
102
+
103
+ ( possible_partial, downloaded_so_far)
104
+ } else {
105
+ println ! ( "Download resume not supported" ) ;
106
+ ( try!( OpenOptions :: new ( )
107
+ . write ( true )
108
+ . create ( true )
109
+ . open ( & path)
110
+ . chain_err ( || "error creating file for download" ) ) , 0 )
111
+ } ;
75
112
76
- try!( download_with_backend ( backend, url, & |event| {
113
+ let file = RefCell :: new ( file) ;
114
+
115
+ try!( download_with_backend ( backend, url, resume_from, & |event| {
77
116
if let Event :: DownloadDataReceived ( data) = event {
78
117
try!( file. borrow_mut ( ) . write_all ( data)
79
118
. chain_err ( || "unable to write download to disk" ) ) ;
@@ -89,11 +128,8 @@ pub fn download_to_path_with_backend(
89
128
90
129
Ok ( ( ) )
91
130
} ( ) . map_err ( |e| {
92
- if path. is_file ( ) {
93
- // FIXME ignoring compound errors
94
- let _ = fs:: remove_file ( path) ;
95
- }
96
131
132
+ // TODO is there any point clearing up here? What kind of errors will leave us with an unusable partial?
97
133
e
98
134
} )
99
135
}
@@ -114,6 +150,7 @@ pub mod curl {
114
150
use super :: Event ;
115
151
116
152
pub fn download ( url : & Url ,
153
+ resume_from : u64 ,
117
154
callback : & Fn ( Event ) -> Result < ( ) > )
118
155
-> Result < ( ) > {
119
156
// Fetch either a cached libcurl handle (which will preserve open
@@ -128,6 +165,12 @@ pub mod curl {
128
165
try!( handle. url ( & url. to_string ( ) ) . chain_err ( || "failed to set url" ) ) ;
129
166
try!( handle. follow_location ( true ) . chain_err ( || "failed to set follow redirects" ) ) ;
130
167
168
+ if resume_from > 0 {
169
+ try!( handle. range ( & ( resume_from. to_string ( ) + "-" ) ) . chain_err ( || "setting the range-header for download resumption" ) ) ;
170
+ } else {
171
+ try!( handle. range ( "" ) . chain_err ( || "clearing range header" ) ) ;
172
+ }
173
+
131
174
// Take at most 30s to connect
132
175
try!( handle. connect_timeout ( Duration :: new ( 30 , 0 ) ) . chain_err ( || "failed to set connect timeout" ) ) ;
133
176
@@ -154,8 +197,8 @@ pub mod curl {
154
197
if let Ok ( data) = str:: from_utf8 ( header) {
155
198
let prefix = "Content-Length: " ;
156
199
if data. starts_with ( prefix) {
157
- if let Ok ( s) = data[ prefix. len ( ) ..] . trim ( ) . parse ( ) {
158
- let msg = Event :: DownloadContentLengthReceived ( s) ;
200
+ if let Ok ( s) = data[ prefix. len ( ) ..] . trim ( ) . parse :: < u64 > ( ) {
201
+ let msg = Event :: DownloadContentLengthReceived ( s + resume_from ) ;
159
202
match callback ( msg) {
160
203
Ok ( ( ) ) => ( ) ,
161
204
Err ( e) => {
@@ -188,10 +231,11 @@ pub mod curl {
188
231
} ) ) ;
189
232
}
190
233
191
- // If we didn't get a 200 or 0 ("OK" for files) then return an error
234
+ // If we didn't get a 20x or 0 ("OK" for files) then return an error
192
235
let code = try!( handle. response_code ( ) . chain_err ( || "failed to get response code" ) ) ;
193
- if code != 200 && code != 0 {
194
- return Err ( ErrorKind :: HttpStatus ( code) . into ( ) ) ;
236
+ match code {
237
+ 0 | 200 ...299 => { println ! ( "status code: {}" , code) }
238
+ _ => { return Err ( ErrorKind :: HttpStatus ( code) . into ( ) ) ; }
195
239
}
196
240
197
241
Ok ( ( ) )
@@ -639,6 +683,7 @@ pub mod curl {
639
683
use super :: Event ;
640
684
641
685
pub fn download ( _url : & Url ,
686
+ _resume_from : u64 ,
642
687
_callback : & Fn ( Event ) -> Result < ( ) > )
643
688
-> Result < ( ) > {
644
689
Err ( ErrorKind :: BackendUnavailable ( "curl" ) . into ( ) )
0 commit comments