• Модуль: disk
  • Путь к файлу: ~/bitrix/modules/disk/lib/document/upload/resumableupload.php
  • Класс: BitrixDiskDocumentUploadResumableUpload
  • Вызов: ResumableUpload::upload
public function upload()
{
	if(!$this->checkRequiredInputParams($this->fileData->toArray(), array(
		'src', 'mimeType',
	)))
	{
		return false;
	}

	if(!$this->fileData->getSize())
	{
		$this->fileData->setSize(filesize($this->fileData->getSrc()));
	}

	$chunkSize = self::CHUNK_SIZE;
	$locationForUpload = $this->getLocationForResumableUpload();
	if(!$locationForUpload)
	{
		return false;
	}

	/** @var HttpClient $http */
	$http = null;
	$lastResponseCode = false;
	$fileMetadata = null;
	$lastRange = false;
	$transactionCounter = 0;
	$doExponentialBackoff = false;
	$exponentialBackoffCounter = 0;
	$response = array();

	while ($lastResponseCode === false || $lastResponseCode == 308 || $lastResponseCode == 202)
	{
		$transactionCounter++;

		if ($doExponentialBackoff)
		{
			$sleepFor = pow(2, $exponentialBackoffCounter);
			sleep($sleepFor);
			usleep(rand(0, 1000));
			$exponentialBackoffCounter++;
			if ($exponentialBackoffCounter > 5)
			{
				$this->lastStatus = $http? $http->getStatus() : 0;
				$this->errorCollection[] = new Error(
					"Could not upload part (Exponential back off) ({$this->lastStatus})",
					self::ERROR_HTTP_RESUMABLE_UPLOAD
				);

				return false;
			}
		}

		// determining what range is next
		$rangeStart = $this->getNextStartRange($http);
		$rangeEnd = min($chunkSize, $this->fileData->getSize() - 1);

		if ($rangeStart > 0)
		{
			$rangeEnd = min($rangeStart + $chunkSize, $this->fileData->getSize() - 1);
		}

		$http = new HttpClient(array(
			'socketTimeout' => 10,
			'streamTimeout' => 30,
			'version' => HttpClient::HTTP_1_1,
		));
		$this->setBearer($http);
		$http->setHeader('Content-Length', (string)($rangeEnd - $rangeStart + 1));
		$http->setHeader('Content-Type', $this->fileData->getMimeType());
		$http->setHeader('Content-Range', "bytes {$rangeStart}-{$rangeEnd}/{$this->fileData->getSize()}");

		$toSendContent = file_get_contents($this->fileData->getSrc(), false, null, $rangeStart, ($rangeEnd - $rangeStart + 1));
		if($http->query('PUT', $locationForUpload, $toSendContent))
		{
			$response['headers']['range'] = $http->getHeaders()->get('Range');
		}

		$doExponentialBackoff = false;
		if ($http->getStatus())
		{
			// checking for expired credentials
			if ($http->getStatus() == 401)
			{
				$this->documentHandler->queryAccessToken();
				$lastResponseCode = false;
			}
			else if ($http->getStatus() == 308 || $http->getStatus() == 202)
			{
				// todo: verify x-range-md5 header to be sure
				$lastResponseCode = $http->getStatus();
				$exponentialBackoffCounter = 0;
			}
			else if ($http->getStatus() == 503)
			{ // Google's letting us know we should retry
				$doExponentialBackoff = true;
				$lastResponseCode = false;
			}
			else
			{
				if (in_array($http->getStatus(), array(200, 201)))
				{ // we are done!
					$lastResponseCode = $http->getStatus();
				}
				else
				{
					$this->lastStatus = $http->getStatus();
					$this->errorCollection[] = new Error(
						"Could not upload part ({$this->lastStatus})",
						self::ERROR_HTTP_RESUMABLE_UPLOAD
					);

					return false;
				}
			}
		}
		else
		{
			$doExponentialBackoff = true;
			$lastResponseCode = false;
		}
	}

	if ($lastResponseCode != 200 && $lastResponseCode != 201)
	{
		$this->lastStatus = $http->getStatus();
		$this->errorCollection[] = new Error(
			"Could not upload final part ({$this->lastStatus})",
			self::ERROR_HTTP_RESUMABLE_UPLOAD
		);

		return false;
	}

	$this->lastResponse = null;
	if(isset($http))
	{
		$this->lastResponse = Json::decode($http->getResult());
	}
	if($this->lastResponse === null)
	{
		$this->errorCollection[] = new Error(
			'Could not decode response as json', self::ERROR_BAD_JSON
		);
		return false;
	}

	$this->fillFileDataByResponse($this->fileData, $this->lastResponse);

	return true;
}