] // 23% [========> ] // 15% [=====> ] // // Note: The server needs to send a content-length header for progress updates to work. use Curl\MultiCurl; // Keep track of download progress for each of the downloads. $download_status = []; // Keep track of when the screen was last updated so it not updated too frequently. $last_updated_time = 0; $multi_curl = new MultiCurl(); $urls_to_download = [ 'https://www.php.net/distributions/manual/php_manual_en.html.gz', 'https://www.php.net/distributions/manual/php_manual_en.tar.gz', 'https://www.php.net/distributions/manual/php_manual_en.chm', ]; $i = 0; foreach ($urls_to_download as $url) { $filename = basename($url); echo 'will be downloading ' . $url . ' and saving as "' . $filename . '"' . "\n"; $download_status[$i] = [ 'position' => $i, 'complete' => false, 'filename' => $filename, 'size' => 0, 'downloaded' => 0, ]; $curl = $multi_curl->addDownload($url, $filename); // Increase timeout to avoid error: // "Operation timed out after 30000 milliseconds with ... out of ... bytes // received". $curl->setTimeout(500); // Slow the download. Comment the following lines to remove the download // throttling. $curl->setOpt(CURLOPT_MAX_RECV_SPEED_LARGE, 500000); $curl->setOpt(CURLOPT_BUFFERSIZE, 1024); $curl->progress(function ( $client, $download_size, $downloaded, $upload_size, $uploaded ) use ( $i, &$download_status, &$last_updated_time ) { if ($download_size === 0) { return 0; } $download_completed = $downloaded === $download_size; $current_time = time(); // Avoid sending an update if we're within the same second and the // download has not yet completed. if (!$download_completed && $current_time === $last_updated_time) { return 0; } $last_updated_time = $current_time; // Update progress of this download. $download_status[$i]['complete'] = $download_completed; $download_status[$i]['size'] = $download_size; $download_status[$i]['downloaded'] = $downloaded; // Generate response including completion status of all downloads and // status of each individual download. $response = [ 'status' => '', 'downloads' => [], ]; $all_downloads_completed = true; foreach ($download_status as $key => $value) { $response['downloads'][] = $value; $all_downloads_completed = $all_downloads_completed && $value['complete']; } $response['status'] = $all_downloads_completed ? 'done' : 'active'; $json_response = json_encode($response); $out = fopen('/tmp/myfifo', 'w'); // TODO: Catch broken pipe: // PHP Notice: fwrite(): Write of 52 bytes failed with errno=32 // Broken pipe in ./multi_curl_progress_advanced.php on line [...] fwrite($out, $json_response . "\n"); fclose($out); // Comment the following line to hide the download progress updates // being sent to the named pipe. echo $json_response . "\n"; return 0; }); $i += 1; } echo 'starting download' . "\n"; $multi_curl->start(); echo 'all done' . "\n";