Piwik\Http::downloadChunk PHP Method

downloadChunk() public static method

_Note: this function uses the **Range** HTTP header to accomplish downloading in parts. Not every server supports this header._ The proper use of this function is to call it once per request. The browser should continue to send requests to Piwik which will in turn call this method until the file has completely downloaded. In this way, the user can be informed of a download's progress. **Example Usage** browser JavaScript var downloadFile = function (isStart) { var ajax = new ajaxHelper(); ajax.addParams({ module: 'MyPlugin', action: 'myAction', isStart: isStart ? 1 : 0 }, 'post'); ajax.setCallback(function (response) { var progress = response.progress ...update progress... downloadFile(false); }); ajax.send(); } downloadFile(true); PHP controller action public function myAction() { $outputPath = PIWIK_INCLUDE_PATH . '/tmp/averybigfile.zip'; $isStart = Common::getRequestVar('isStart', 1, 'int'); Http::downloadChunk("http://bigfiles.com/averybigfile.zip", $outputPath, $isStart == 1); }
public static downloadChunk ( string $url, string $outputPath, boolean $isContinuation ) : array
$url string The url to download from.
$outputPath string The path to the file to save/append to.
$isContinuation boolean `true` if this is the continuation of a download, or if we're starting a fresh one.
return array
    public static function downloadChunk($url, $outputPath, $isContinuation)
    {
        // make sure file doesn't already exist if we're starting a new download
        if (!$isContinuation && file_exists($outputPath)) {
            throw new Exception(Piwik::translate('General_DownloadFail_FileExists', "'" . $outputPath . "'") . ' ' . Piwik::translate('General_DownloadPleaseRemoveExisting'));
        }
        // if we're starting a download, get the expected file size & save as an option
        $downloadOption = $outputPath . '_expectedDownloadSize';
        if (!$isContinuation) {
            $expectedFileSizeResult = Http::sendHttpRequest($url, $timeout = 300, $userAgent = null, $destinationPath = null, $followDepth = 0, $acceptLanguage = false, $byteRange = false, $getExtendedInfo = true, $httpMethod = 'HEAD');
            $expectedFileSize = 0;
            if (isset($expectedFileSizeResult['headers']['Content-Length'])) {
                $expectedFileSize = (int) $expectedFileSizeResult['headers']['Content-Length'];
            }
            if ($expectedFileSize == 0) {
                Log::info("HEAD request for '%s' failed, got following: %s", $url, print_r($expectedFileSizeResult, true));
                throw new Exception(Piwik::translate('General_DownloadFail_HttpRequestFail'));
            }
            Option::set($downloadOption, $expectedFileSize);
        } else {
            $expectedFileSize = (int) Option::get($downloadOption);
            if ($expectedFileSize === false) {
                // sanity check
                throw new Exception("Trying to continue a download that never started?! That's not supposed to happen...");
            }
        }
        // if existing file is already big enough, then fail so we don't accidentally overwrite
        // existing DB
        $existingSize = file_exists($outputPath) ? filesize($outputPath) : 0;
        if ($existingSize >= $expectedFileSize) {
            throw new Exception(Piwik::translate('General_DownloadFail_FileExistsContinue', "'" . $outputPath . "'") . ' ' . Piwik::translate('General_DownloadPleaseRemoveExisting'));
        }
        // download a chunk of the file
        $result = Http::sendHttpRequest($url, $timeout = 300, $userAgent = null, $destinationPath = null, $followDepth = 0, $acceptLanguage = false, $byteRange = array($existingSize, min($existingSize + 1024 * 1024 - 1, $expectedFileSize)), $getExtendedInfo = true);
        if ($result === false || $result['status'] < 200 || $result['status'] > 299) {
            $result['data'] = self::truncateStr($result['data'], 1024);
            Log::info("Failed to download range '%s-%s' of file from url '%s'. Got result: %s", $byteRange[0], $byteRange[1], $url, print_r($result, true));
            throw new Exception(Piwik::translate('General_DownloadFail_HttpRequestFail'));
        }
        // write chunk to file
        $f = fopen($outputPath, 'ab');
        fwrite($f, $result['data']);
        fclose($f);
        clearstatcache($clear_realpath_cache = true, $outputPath);
        return array('current_size' => filesize($outputPath), 'expected_file_size' => $expectedFileSize);
    }

Usage Example

Example #1
0
 /**
  * Starts or continues a download for a missing GeoIP database. A database is missing if
  * it has an update URL configured, but the actual database is not available in the misc
  * directory.
  *
  * Input:
  *   'url' - The URL to download the database from.
  *   'continue' - 1 if we're continuing a download, 0 if we're starting one.
  *
  * Output:
  *   'error' - If an error occurs this describes the error.
  *   'to_download' - The URL of a missing database that should be downloaded next (if any).
  *   'to_download_label' - The label to use w/ the progress bar that describes what we're
  *                         downloading.
  *   'current_size' - Size of the current file on disk.
  *   'expected_file_size' - Size of the completely downloaded file.
  */
 public function downloadMissingGeoIpDb()
 {
     $this->dieIfGeolocationAdminIsDisabled();
     Piwik::checkUserHasSuperUserAccess();
     if ($_SERVER["REQUEST_METHOD"] == "POST") {
         try {
             $this->checkTokenInUrl();
             Json::sendHeaderJSON();
             // based on the database type (provided by the 'key' query param) determine the
             // url & output file name
             $key = Common::getRequestVar('key', null, 'string');
             $url = GeoIPAutoUpdater::getConfiguredUrl($key);
             $ext = GeoIPAutoUpdater::getGeoIPUrlExtension($url);
             $filename = GeoIp::$dbNames[$key][0] . '.' . $ext;
             if (substr($filename, 0, 15) == 'GeoLiteCity.dat') {
                 $filename = 'GeoIPCity.dat' . substr($filename, 15);
             }
             $outputPath = GeoIp::getPathForGeoIpDatabase($filename);
             // download part of the file
             $result = Http::downloadChunk($url, $outputPath, Common::getRequestVar('continue', true, 'int'));
             // if the file is done
             if ($result['current_size'] >= $result['expected_file_size']) {
                 GeoIPAutoUpdater::unzipDownloadedFile($outputPath, $unlink = true);
                 $info = $this->getNextMissingDbUrlInfo();
                 if ($info !== false) {
                     return json_encode($info);
                 }
             }
             return json_encode($result);
         } catch (Exception $ex) {
             return json_encode(array('error' => $ex->getMessage()));
         }
     }
 }