You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
317 lines
10 KiB
PHTML
317 lines
10 KiB
PHTML
2 years ago
|
<?php
|
||
|
|
||
|
namespace qcloudcos;
|
||
|
|
||
|
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'conf.php');
|
||
|
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'error_code.php');
|
||
|
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'http_client.php');
|
||
|
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'libcurl_helper.php');
|
||
|
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'libcurl_wrapper.php');
|
||
|
|
||
|
/**
|
||
|
* Uploading file to cos slice by slice.
|
||
|
*/
|
||
|
class SliceUploading {
|
||
|
// default task number for concurrently uploading slices.
|
||
|
const DEFAULT_CONCURRENT_TASK_NUMBER = 3;
|
||
|
|
||
|
private $timeoutMs; // int: timeout in milliseconds for each http request.
|
||
|
private $maxRetryCount; // int: max retry count on failure.
|
||
|
|
||
|
private $errorCode; // int: last error code.
|
||
|
private $errorMessage; // string: last error message.
|
||
|
private $requestId; // string: request id for last http request.
|
||
|
private $signature; // string: signature for auth.
|
||
|
private $srcFpath; // string: source file path for uploading.
|
||
|
private $url; // string: destination url for uploading.
|
||
|
private $fileSize; // int: source file size.
|
||
|
private $sliceSize; // int: slice size for each upload.
|
||
|
private $session; // string: session for each upload transaction.
|
||
|
private $concurrentTaskNumber; // int: concurrent uploading task number.
|
||
|
|
||
|
private $offset; // int: current uploading offset.
|
||
|
private $libcurlWrapper; // LibcurlWrapper: curl wrapper for sending multi http request concurrently.
|
||
|
|
||
|
private $accessUrl; // string: access url.
|
||
|
private $resourcePath; // string: resource path.
|
||
|
private $sourceUrl; // string: source url.
|
||
|
|
||
|
/**
|
||
|
* timeoutMs: max timeout in milliseconds for each http request.
|
||
|
* maxRetryCount: max retry count for uploading each slice on error.
|
||
|
*/
|
||
|
public function __construct($timeoutMs, $maxRetryCount) {
|
||
|
$this->timeoutMs = $timeoutMs;
|
||
|
$this->maxRetryCount = $maxRetryCount;
|
||
|
$this->errorCode = COSAPI_SUCCESS;
|
||
|
$this->errorMessage = '';
|
||
|
$this->concurrentTaskNumber = self::DEFAULT_CONCURRENT_TASK_NUMBER;
|
||
|
|
||
|
$this->offset = 0;
|
||
|
|
||
|
$this->libcurlWrapper = new LibcurlWrapper();
|
||
|
}
|
||
|
|
||
|
public function __destruct() {
|
||
|
}
|
||
|
|
||
|
public function getLastErrorCode() {
|
||
|
return $this->errorCode;
|
||
|
}
|
||
|
|
||
|
public function getLastErrorMessage() {
|
||
|
return $this->errorMessage;
|
||
|
}
|
||
|
|
||
|
public function getRequestId() {
|
||
|
return $this->requestId;
|
||
|
}
|
||
|
|
||
|
public function getAccessUrl() {
|
||
|
return $this->accessUrl;
|
||
|
}
|
||
|
|
||
|
public function getResourcePath() {
|
||
|
return $this->resourcePath;
|
||
|
}
|
||
|
|
||
|
public function getSourceUrl() {
|
||
|
return $this->sourceUrl;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return true on success and return false on failure.
|
||
|
*/
|
||
|
public function initUploading(
|
||
|
$signature, $srcFpath, $url, $fileSize, $sliceSize, $bizAttr, $insertOnly) {
|
||
|
$this->signature = $signature;
|
||
|
$this->srcFpath = $srcFpath;
|
||
|
$this->url = $url;
|
||
|
$this->fileSize = $fileSize;
|
||
|
$this->sliceSize = $sliceSize;
|
||
|
|
||
|
// Clear error so caller can successfully retry.
|
||
|
$this->clearError();
|
||
|
|
||
|
$request = array(
|
||
|
'url' => $url,
|
||
|
'method' => 'post',
|
||
|
'timeout' => $this->timeoutMs / 1000,
|
||
|
'data' => array(
|
||
|
'op' => 'upload_slice_init',
|
||
|
'filesize' => $fileSize,
|
||
|
'slice_size' => $sliceSize,
|
||
|
'insertOnly' => $insertOnly,
|
||
|
),
|
||
|
'header' => array(
|
||
|
'Authorization: ' . $signature,
|
||
|
),
|
||
|
);
|
||
|
|
||
|
if (isset($bizAttr) && strlen($bizAttr)) {
|
||
|
$request['data']['biz_attr'] = $bizAttr;
|
||
|
}
|
||
|
|
||
|
$response = $this->sendRequest($request);
|
||
|
if ($response === false) {
|
||
|
return false;
|
||
|
}
|
||
|
$this->session = $response['data']['session'];
|
||
|
|
||
|
if (isset($response['data']['slice_size'])) {
|
||
|
$this->sliceSize = $response['data']['slice_size'];
|
||
|
}
|
||
|
|
||
|
if (isset($response['data']['serial_upload']) && $response['data']['serial_upload'] == 1) {
|
||
|
$this->concurrentTaskNumber = 1;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return true on success and return false on failure.
|
||
|
*/
|
||
|
public function performUploading() {
|
||
|
for ($i = 0; $i < $this->concurrentTaskNumber; ++$i) {
|
||
|
if ($this->offset >= $this->fileSize) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$sliceContent = file_get_contents($this->srcFpath, false, null, $this->offset, $this->sliceSize);
|
||
|
if ($sliceContent === false) {
|
||
|
$this->setError(COSAPI_PARAMS_ERROR, 'read file ' . $this->srcFpath . ' error');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$request = new HttpRequest();
|
||
|
$request->timeoutMs = $this->timeoutMs;
|
||
|
$request->url = $this->url;
|
||
|
$request->method = 'POST';
|
||
|
$request->customHeaders = array(
|
||
|
'Authorization: ' . $this->signature,
|
||
|
);
|
||
|
$request->dataToPost = array(
|
||
|
'op' => 'upload_slice_data',
|
||
|
'session' => $this->session,
|
||
|
'offset' => $this->offset,
|
||
|
'filecontent' => $sliceContent,
|
||
|
'datamd5' => md5($sliceContent),
|
||
|
);
|
||
|
$request->userData = array(
|
||
|
'retryCount' => 0,
|
||
|
);
|
||
|
|
||
|
$this->libcurlWrapper->startSendingRequest($request, array($this, 'uploadCallback'));
|
||
|
|
||
|
$this->offset += $this->sliceSize;
|
||
|
}
|
||
|
|
||
|
$this->libcurlWrapper->performSendingRequest();
|
||
|
|
||
|
if ($this->errorCode !== COSAPI_SUCCESS) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return true on success and return false on failure.
|
||
|
*/
|
||
|
public function finishUploading() {
|
||
|
$request = array(
|
||
|
'url' => $this->url,
|
||
|
'method' => 'post',
|
||
|
'timeout' => $this->timeoutMs / 1000,
|
||
|
'data' => array(
|
||
|
'op' => 'upload_slice_finish',
|
||
|
'session' => $this->session,
|
||
|
'filesize' => $this->fileSize,
|
||
|
),
|
||
|
'header' => array(
|
||
|
'Authorization: ' . $this->signature,
|
||
|
),
|
||
|
);
|
||
|
|
||
|
$response = $this->sendRequest($request);
|
||
|
if ($response === false) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$this->accessUrl = $response['data']['access_url'];
|
||
|
$this->resourcePath = $response['data']['resource_path'];
|
||
|
$this->sourceUrl = $response['data']['source_url'];
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private function sendRequest($request) {
|
||
|
$response = HttpClient::sendRequest($request);
|
||
|
if ($response === false) {
|
||
|
$this->setError(COSAPI_NETWORK_ERROR, 'network error');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$responseJson = json_decode($response, true);
|
||
|
if ($responseJson === NULL) {
|
||
|
$this->setError(COSAPI_NETWORK_ERROR, 'network error');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$this->requestId = $responseJson['request_id'];
|
||
|
if ($responseJson['code'] != 0) {
|
||
|
$this->setError($responseJson['code'], $responseJson['message']);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return $responseJson;
|
||
|
}
|
||
|
|
||
|
private function clearError() {
|
||
|
$this->errorCode = COSAPI_SUCCESS;
|
||
|
$this->errorMessage = 'success';
|
||
|
}
|
||
|
|
||
|
private function setError($errorCode, $errorMessage) {
|
||
|
$this->errorCode = $errorCode;
|
||
|
$this->errorMessage = $errorMessage;
|
||
|
}
|
||
|
|
||
|
public function uploadCallback($request, $response) {
|
||
|
if ($this->errorCode !== COSAPI_SUCCESS) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$requestErrorCode = COSAPI_SUCCESS;
|
||
|
$requestErrorMessage = 'success';
|
||
|
$retryCount = $request->userData['retryCount'];
|
||
|
|
||
|
$responseJson = json_decode($response->body, true);
|
||
|
if ($responseJson === NULL) {
|
||
|
$requestErrorCode = COSAPI_NETWORK_ERROR;
|
||
|
$requestErrorMessage = 'network error';
|
||
|
}
|
||
|
|
||
|
if ($response->curlErrorCode !== CURLE_OK) {
|
||
|
$requestErrorCode = COSAPI_NETWORK_ERROR;
|
||
|
$requestErrorMessage = 'network error: curl errno ' . $response->curlErrorCode;
|
||
|
}
|
||
|
|
||
|
$this->requestId = $responseJson['request_id'];
|
||
|
if ($responseJson['code'] != 0) {
|
||
|
$requestErrorCode = $responseJson['code'];
|
||
|
$requestErrorMessage = $responseJson['message'];
|
||
|
}
|
||
|
|
||
|
if (isset($responseJson['data']['datamd5']) &&
|
||
|
$responseJson['data']['datamd5'] !== $request->dataToPost['datamd5']) {
|
||
|
$requestErrorCode = COSAPI_INTEGRITY_ERROR;
|
||
|
$requestErrorMessage = 'cosapi integrity error';
|
||
|
}
|
||
|
|
||
|
if ($requestErrorCode !== COSAPI_SUCCESS) {
|
||
|
if ($retryCount >= $this->maxRetryCount) {
|
||
|
$this->setError($requestErrorCode, $requestErrorMessage);
|
||
|
} else {
|
||
|
$request->userData['retryCount'] += 1;
|
||
|
$this->libcurlWrapper->startSendingRequest($request, array($this, 'uploadCallback'));
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ($this->offset >= $this->fileSize) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Send next slice.
|
||
|
$nextSliceContent = file_get_contents($this->srcFpath, false, null, $this->offset, $this->sliceSize);
|
||
|
if ($nextSliceContent === false) {
|
||
|
$this->setError(COSAPI_PARAMS_ERROR, 'read file ' . $this->srcFpath . ' error');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$nextSliceRequest = new HttpRequest();
|
||
|
$nextSliceRequest->timeoutMs = $this->timeoutMs;
|
||
|
$nextSliceRequest->url = $this->url;
|
||
|
$nextSliceRequest->method = 'POST';
|
||
|
$nextSliceRequest->customHeaders = array(
|
||
|
'Authorization: ' . $this->signature,
|
||
|
);
|
||
|
$nextSliceRequest->dataToPost = array(
|
||
|
'op' => 'upload_slice_data',
|
||
|
'session' => $this->session,
|
||
|
'offset' => $this->offset,
|
||
|
'filecontent' => $nextSliceContent,
|
||
|
'datamd5' => md5($nextSliceContent),
|
||
|
);
|
||
|
$nextSliceRequest->userData = array(
|
||
|
'retryCount' => 0,
|
||
|
);
|
||
|
|
||
|
$this->libcurlWrapper->startSendingRequest($nextSliceRequest, array($this, 'uploadCallback'));
|
||
|
|
||
|
$this->offset += $this->sliceSize;
|
||
|
}
|
||
|
}
|