@@ -373,19 +373,23 @@ public function download($url, $mixed_filename)
373
373
*/
374
374
public function fastDownload ($ url , $ filename , $ connections = 4 )
375
375
{
376
- // Retrieve content length from the "Content-Length" header and use an
377
- // HTTP GET request because not all hosts support HEAD requests.
378
- $ this ->setOpts ([
379
- CURLOPT_CUSTOMREQUEST => 'GET ' ,
380
- CURLOPT_NOBODY => true ,
381
- CURLOPT_HEADER => true ,
382
- CURLOPT_ENCODING => '' ,
383
- ]);
384
- $ this ->setUrl ($ url );
385
- $ this ->exec ();
376
+ // Retrieve content length from the "Content-Length" header from the url
377
+ // to download. Use an HTTP GET request without a body instead of a HEAD
378
+ // request because not all hosts support HEAD requests.
379
+ $ curl = new Curl ();
380
+ $ curl ->setOptInternal (CURLOPT_NOBODY , true );
381
+
382
+ // Pass user-specified options to the instance checking for content-length.
383
+ $ curl ->setOpts ($ this ->userSetOptions );
384
+ $ curl ->get ($ url );
385
+
386
+ // Exit early when an error occurred.
387
+ if ($ curl ->error ) {
388
+ return false ;
389
+ }
386
390
387
- $ content_length = isset ($ this ->responseHeaders ['Content-Length ' ]) ?
388
- $ this ->responseHeaders ['Content-Length ' ] : null ;
391
+ $ content_length = isset ($ curl ->responseHeaders ['Content-Length ' ]) ?
392
+ $ curl ->responseHeaders ['Content-Length ' ] : null ;
389
393
390
394
// Use a regular download when content length could not be determined.
391
395
if (!$ content_length ) {
@@ -395,25 +399,21 @@ public function fastDownload($url, $filename, $connections = 4)
395
399
// Divide chunk_size across the number of connections.
396
400
$ chunk_size = ceil ($ content_length / $ connections );
397
401
398
- // First bytes.
399
- $ offset = 0 ;
400
- $ next_chunk = $ chunk_size ;
401
-
402
402
// Keep track of file name parts.
403
403
$ part_file_names = [];
404
404
405
405
$ multi_curl = new MultiCurl ();
406
406
$ multi_curl ->setConcurrency ($ connections );
407
- $ multi_curl ->error (function ($ instance ) {
408
- return false ;
409
- });
410
407
411
- for ($ i = 1 ; $ i <= $ connections ; $ i ++) {
412
- // If last chunk then no need to supply it.
413
- // Range starts with 0, so subtract 1.
414
- $ next_chunk = $ i === $ connections ? '' : $ next_chunk - 1 ;
408
+ for ($ part_number = 1 ; $ part_number <= $ connections ; $ part_number ++) {
409
+ $ range_start = ($ part_number - 1 ) * $ chunk_size ;
410
+ $ range_end = $ range_start + $ chunk_size - 1 ;
411
+ if ($ part_number === $ connections ) {
412
+ $ range_end = '' ;
413
+ }
414
+ $ range = $ range_start . '- ' . $ range_end ;
415
415
416
- $ part_file_name = $ filename . '.part ' . $ i ;
416
+ $ part_file_name = $ filename . '.part ' . $ part_number ;
417
417
418
418
// Save the file name of this part.
419
419
$ part_file_names [] = $ part_file_name ;
@@ -424,25 +424,28 @@ public function fastDownload($url, $filename, $connections = 4)
424
424
}
425
425
426
426
// Create file part.
427
- $ file_handle = fopen ( $ part_file_name , ' w ' );
427
+ $ file_handle = tmpfile ( );
428
428
429
+ // Setup the instance downloading a part.
429
430
$ curl = new Curl ();
430
- $ curl ->setOpt (CURLOPT_ENCODING , '' );
431
- $ curl ->setRange ($ offset . '- ' . $ next_chunk );
432
- $ curl ->setFile ($ file_handle );
433
- $ curl ->disableTimeout (); // otherwise download may fail.
434
431
$ curl ->setUrl ($ url );
435
432
436
- $ curl ->complete (function () use ($ file_handle ) {
437
- fclose ($ file_handle );
438
- });
433
+ // Pass user-specified options to the instance downloading a part.
434
+ $ curl ->setOpts ($ this ->userSetOptions );
439
435
440
- $ multi_curl ->addCurl ($ curl );
436
+ $ curl ->setOptInternal (CURLOPT_CUSTOMREQUEST , 'GET ' );
437
+ $ curl ->setOptInternal (CURLOPT_HTTPGET , true );
438
+ $ curl ->setRangeInternal ($ range );
439
+ $ curl ->setFileInternal ($ file_handle );
440
+ $ curl ->fileHandle = $ file_handle ;
441
441
442
- if ($ i !== $ connections ) {
443
- $ offset = $ next_chunk + 1 ; // Add 1 to match offset.
444
- $ next_chunk = $ next_chunk + $ chunk_size ;
445
- }
442
+ $ curl ->downloadCompleteCallback = function ($ instance , $ tmpfile ) use ($ part_file_name ) {
443
+ $ fh = fopen ($ part_file_name , 'wb ' );
444
+ stream_copy_to_stream ($ tmpfile , $ fh );
445
+ fclose ($ fh );
446
+ };
447
+
448
+ $ multi_curl ->addCurl ($ curl );
446
449
}
447
450
448
451
// Start the simultaneous downloads for each of the ranges in parallel.
@@ -457,7 +460,15 @@ public function fastDownload($url, $filename, $connections = 4)
457
460
$ main_file_handle = fopen ($ filename , 'w ' );
458
461
459
462
foreach ($ part_file_names as $ part_file_name ) {
463
+ if (!is_file ($ part_file_name )) {
464
+ return false ;
465
+ }
466
+
460
467
$ file_handle = fopen ($ part_file_name , 'r ' );
468
+ if ($ file_handle === false ) {
469
+ return false ;
470
+ }
471
+
461
472
stream_copy_to_stream ($ file_handle , $ main_file_handle );
462
473
fclose ($ file_handle );
463
474
unlink ($ part_file_name );
@@ -1156,6 +1167,9 @@ protected function setOptInternal($option, $value)
1156
1167
*/
1157
1168
public function setOpts ($ options )
1158
1169
{
1170
+ if (!count ($ options )) {
1171
+ return true ;
1172
+ }
1159
1173
foreach ($ options as $ option => $ value ) {
1160
1174
if (!$ this ->setOpt ($ option , $ value )) {
1161
1175
return false ;
@@ -1799,6 +1813,9 @@ function ($key) use ($curl_const_prefix) {
1799
1813
echo "\n" ;
1800
1814
} elseif (is_bool ($ value )) {
1801
1815
echo ' ' . ($ value ? 'true ' : 'false ' ) . "\n" ;
1816
+ } elseif (is_array ($ value )) {
1817
+ echo ' ' ;
1818
+ var_dump ($ value );
1802
1819
} elseif (is_callable ($ value )) {
1803
1820
echo ' (callable) ' . "\n" ;
1804
1821
} else {
0 commit comments