@@ -140,7 +140,7 @@ def download(self, chunks=1, resume=False):
140
140
141
141
return self ._download (chunks , False )
142
142
else :
143
- raise e
143
+ raise
144
144
finally :
145
145
self .close ()
146
146
@@ -161,7 +161,7 @@ def _download(self, chunks, resume):
161
161
162
162
lastFinishCheck = 0
163
163
lastTimeCheck = 0
164
- chunksDone = set ()
164
+ chunksDone = set () # list of curl handles that are finished
165
165
chunksCreated = False
166
166
done = False
167
167
if self .info .getCount () > 1 : # This is a resume, if we were chunked originally assume still can
@@ -202,32 +202,76 @@ def _download(self, chunks, resume):
202
202
t = time ()
203
203
204
204
# reduce these calls
205
- while lastFinishCheck + 1 < t :
205
+ while lastFinishCheck + 0.5 < t :
206
+ # list of failed curl handles
207
+ failed = []
208
+ ex = None # save only last exception, we can only raise one anyway
209
+
206
210
num_q , ok_list , err_list = self .m .info_read ()
207
211
for c in ok_list :
208
- chunksDone .add (c )
212
+ chunk = self .findChunk (c )
213
+ try : # check if the header implies success, else add it to failed list
214
+ chunk .verifyHeader ()
215
+ except BadHeader , e :
216
+ self .log .debug ("Chunk %d failed: %s" % (chunk .id + 1 , str (e )))
217
+ failed .append (chunk )
218
+ ex = e
219
+ else :
220
+ chunksDone .add (c )
221
+
209
222
for c in err_list :
210
223
curl , errno , msg = c
211
- #test if chunk was finished, otherwise raise the exception
224
+ chunk = self .findChunk (curl )
225
+ #test if chunk was finished
212
226
if errno != 23 or "0 !=" not in msg :
213
- raise pycurl .error (errno , msg )
214
-
215
- #@TODO KeyBoardInterrupts are seen as finished chunks,
216
- #but normally not handled to this process, only in the testcase
227
+ failed .append (chunk )
228
+ ex = pycurl .error (errno , msg )
229
+ self .log .debug ("Chunk %d failed: %s" % (chunk .id + 1 , str (ex )))
230
+ continue
231
+
232
+ try : # check if the header implies success, else add it to failed list
233
+ chunk .verifyHeader ()
234
+ except BadHeader , e :
235
+ self .log .debug ("Chunk %d failed: %s" % (chunk .id + 1 , str (e )))
236
+ failed .append (chunk )
237
+ ex = e
238
+ else :
239
+ chunksDone .add (curl )
240
+ if not num_q : # no more infos to get
241
+
242
+ # check if init is not finished so we reset download connections
243
+ # note that other chunks are closed and downloaded with init too
244
+ if failed and init not in failed and init .c not in chunksDone :
245
+ self .log .error (_ ("Download chunks failed, fallback to single connection | %s" % (str (ex ))))
246
+
247
+ #list of chunks to clean and remove
248
+ to_clean = filter (lambda x : x is not init , self .chunks )
249
+ for chunk in to_clean :
250
+ self .closeChunk (chunk )
251
+ self .chunks .remove (chunk )
252
+ remove (self .info .getChunkName (chunk .id ))
253
+
254
+ #let first chunk load the rest and update the info file
255
+ init .resetRange ()
256
+ self .info .clear ()
257
+ self .info .addChunk ("%s.chunk0" % self .filename , (0 , self .size ))
258
+ self .info .save ()
259
+ elif failed :
260
+ raise ex
217
261
218
- chunksDone .add (curl )
219
- if not num_q :
220
262
lastFinishCheck = t
221
263
222
- if len (chunksDone ) == len (self .chunks ):
223
- done = True #all chunks loaded
264
+ if len (chunksDone ) >= len (self .chunks ):
265
+ if len (chunksDone ) > len (self .chunks ):
266
+ self .log .warning ("Finished download chunks size incorrect, please report bug." )
267
+ done = True #all chunks loaded
224
268
225
269
break
226
270
227
271
if done :
228
272
break #all chunks loaded
229
273
230
- # calc speed once per second
274
+ # calc speed once per second, averaging over 3 seconds
231
275
if lastTimeCheck + 1 < t :
232
276
diff = [c .arrived - (self .lastArrived [i ] if len (self .lastArrived ) > i else 0 ) for i , c in
233
277
enumerate (self .chunks )]
@@ -247,15 +291,7 @@ def _download(self, chunks, resume):
247
291
248
292
failed = False
249
293
for chunk in self .chunks :
250
- try :
251
- chunk .verifyHeader ()
252
- except BadHeader , e :
253
- failed = e .code
254
- remove (self .info .getChunkName (chunk .id ))
255
-
256
- chunk .fp .flush ()
257
- fsync (chunk .fp .fileno ()) #make sure everything was written to disk
258
- chunk .fp .close () #needs to be closed, or merging chunks will fail
294
+ chunk .flushFile () #make sure downloads are written to disk
259
295
260
296
if failed : raise BadHeader (failed )
261
297
@@ -265,11 +301,16 @@ def updateProgress(self):
265
301
if self .progressNotify :
266
302
self .progressNotify (self .percent )
267
303
304
+ def findChunk (self , handle ):
305
+ """ linear search to find a chunk (should be ok since chunk size is usually low) """
306
+ for chunk in self .chunks :
307
+ if chunk .c == handle : return chunk
308
+
268
309
def closeChunk (self , chunk ):
269
310
try :
270
311
self .m .remove_handle (chunk .c )
271
- except pycurl .error :
272
- self .log .debug ("Error removing chunk" )
312
+ except pycurl .error , e :
313
+ self .log .debug ("Error removing chunk: %s" % str ( e ) )
273
314
finally :
274
315
chunk .close ()
275
316
0 commit comments