|
@@ -1,9 +1,12 @@
|
|
|
<?php
|
|
|
+
|
|
|
namespace Qiniu\Storage;
|
|
|
|
|
|
use Qiniu\Config;
|
|
|
use Qiniu\Http\Client;
|
|
|
use Qiniu\Http\Error;
|
|
|
+use Qiniu\Enum\SplitUploadVersion;
|
|
|
+use Qiniu\Http\RequestOptions;
|
|
|
|
|
|
/**
|
|
|
* 断点续上传类, 该类主要实现了断点续上传中的分块上传,
|
|
@@ -21,19 +24,34 @@ final class ResumeUploader
|
|
|
private $params;
|
|
|
private $mime;
|
|
|
private $contexts;
|
|
|
+ private $finishedEtags;
|
|
|
private $host;
|
|
|
+ private $bucket;
|
|
|
private $currentUrl;
|
|
|
private $config;
|
|
|
+ private $resumeRecordFile;
|
|
|
+ private $version;
|
|
|
+ private $partSize;
|
|
|
+ /**
|
|
|
+ * @var RequestOptions
|
|
|
+ */
|
|
|
+ private $reqOpt;
|
|
|
|
|
|
/**
|
|
|
* 上传二进制流到七牛
|
|
|
*
|
|
|
- * @param $upToken 上传凭证
|
|
|
- * @param $key 上传文件名
|
|
|
- * @param $inputStream 上传二进制流
|
|
|
- * @param $size 上传流的大小
|
|
|
- * @param $params 自定义变量
|
|
|
- * @param $mime 上传数据的mimeType
|
|
|
+ * @param string $upToken 上传凭证
|
|
|
+ * @param string $key 上传文件名
|
|
|
+ * @param resource $inputStream 上传二进制流
|
|
|
+ * @param int $size 上传流的大小
|
|
|
+ * @param array<string, string> $params 自定义变量
|
|
|
+ * @param string $mime 上传数据的mimeType
|
|
|
+ * @param Config $config
|
|
|
+ * @param string $resumeRecordFile 断点续传的已上传的部分信息记录文件
|
|
|
+ * @param string $version 分片上传版本 目前支持v1/v2版本 默认v1
|
|
|
+ * @param int $partSize 分片上传v2字段 默认大小为4MB 分片大小范围为1 MB - 1 GB
|
|
|
+ * @param RequestOptions $reqOpt 分片上传v2字段 默认大小为4MB 分片大小范围为1 MB - 1 GB
|
|
|
+ * @throws \Exception
|
|
|
*
|
|
|
* @link http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
|
|
|
*/
|
|
@@ -44,7 +62,11 @@ final class ResumeUploader
|
|
|
$size,
|
|
|
$params,
|
|
|
$mime,
|
|
|
- $config
|
|
|
+ $config,
|
|
|
+ $resumeRecordFile = null,
|
|
|
+ $version = 'v1',
|
|
|
+ $partSize = config::BLOCK_SIZE,
|
|
|
+ $reqOpt = null
|
|
|
) {
|
|
|
|
|
|
$this->upToken = $upToken;
|
|
@@ -54,14 +76,29 @@ final class ResumeUploader
|
|
|
$this->params = $params;
|
|
|
$this->mime = $mime;
|
|
|
$this->contexts = array();
|
|
|
+ $this->finishedEtags = array("etags" => array(), "uploadId" => "", "expiredAt" => 0, "uploaded" => 0);
|
|
|
$this->config = $config;
|
|
|
+ $this->resumeRecordFile = $resumeRecordFile ? $resumeRecordFile : null;
|
|
|
+ $this->partSize = $partSize ? $partSize : config::BLOCK_SIZE;
|
|
|
+
|
|
|
+ if ($reqOpt === null) {
|
|
|
+ $reqOpt = new RequestOptions();
|
|
|
+ }
|
|
|
+ $this->reqOpt = $reqOpt;
|
|
|
+
|
|
|
+ try {
|
|
|
+ $this->version = SplitUploadVersion::from($version ? $version : 'v1');
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ throw new \Exception("only support v1/v2 now!", 0, $e);
|
|
|
+ }
|
|
|
|
|
|
list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken);
|
|
|
+ $this->bucket = $bucket;
|
|
|
if ($err != null) {
|
|
|
return array(null, $err);
|
|
|
}
|
|
|
|
|
|
- $upHost = $config->getUpHost($accessKey, $bucket);
|
|
|
+ list($upHost, $err) = $config->getUpHostV2($accessKey, $bucket, $reqOpt);
|
|
|
if ($err != null) {
|
|
|
throw new \Exception($err->message(), 1);
|
|
|
}
|
|
@@ -70,18 +107,102 @@ final class ResumeUploader
|
|
|
|
|
|
/**
|
|
|
* 上传操作
|
|
|
+ * @param $fname string 文件名
|
|
|
+ *
|
|
|
+ * @throws \Exception
|
|
|
*/
|
|
|
public function upload($fname)
|
|
|
{
|
|
|
+ $blkputRets = null;
|
|
|
+ // get upload record from resumeRecordFile
|
|
|
+ if ($this->resumeRecordFile != null) {
|
|
|
+ if (file_exists($this->resumeRecordFile)) {
|
|
|
+ $stream = fopen($this->resumeRecordFile, 'r');
|
|
|
+ if ($stream) {
|
|
|
+ $streamLen = filesize($this->resumeRecordFile);
|
|
|
+ if ($streamLen > 0) {
|
|
|
+ $contents = fread($stream, $streamLen);
|
|
|
+ fclose($stream);
|
|
|
+ if ($contents) {
|
|
|
+ $blkputRets = json_decode($contents, true);
|
|
|
+ if ($blkputRets === null) {
|
|
|
+ error_log("resumeFile contents decode error");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ error_log("read resumeFile failed");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ error_log("resumeFile is empty");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ error_log("resumeFile open failed");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ error_log("resumeFile not exists");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($this->version == SplitUploadVersion::V1) {
|
|
|
+ return $this->uploadV1($fname, $blkputRets);
|
|
|
+ } elseif ($this->version == SplitUploadVersion::V2) {
|
|
|
+ return $this->uploadV2($fname, $blkputRets);
|
|
|
+ } else {
|
|
|
+ throw new \Exception("only support v1/v2 now!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param string $fname 文件名
|
|
|
+ * @param null|array $blkputRets
|
|
|
+ *
|
|
|
+ * @throws \Exception
|
|
|
+ */
|
|
|
+ private function uploadV1($fname, $blkputRets = null)
|
|
|
+ {
|
|
|
+ // 尝试恢复恢复已上传的数据
|
|
|
+ $isResumeUpload = $blkputRets !== null;
|
|
|
+ $this->contexts = array();
|
|
|
+
|
|
|
+ if ($blkputRets) {
|
|
|
+ if (isset($blkputRets['contexts']) && isset($blkputRets['uploaded']) &&
|
|
|
+ is_array($blkputRets['contexts']) && is_int($blkputRets['uploaded'])
|
|
|
+ ) {
|
|
|
+ $this->contexts = array_map(function ($ctx) {
|
|
|
+ if (is_array($ctx)) {
|
|
|
+ return $ctx;
|
|
|
+ } else {
|
|
|
+ // 兼容旧版本(旧版本没有存储 expireAt)
|
|
|
+ return array(
|
|
|
+ "ctx" => $ctx,
|
|
|
+ "expiredAt" => 0,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }, $blkputRets['contexts']);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 上传分片
|
|
|
$uploaded = 0;
|
|
|
while ($uploaded < $this->size) {
|
|
|
$blockSize = $this->blockSize($uploaded);
|
|
|
+ $blockIndex = $uploaded / $this->partSize;
|
|
|
+ if (!is_int($blockIndex)) {
|
|
|
+ throw new \Exception("v1 part size changed");
|
|
|
+ }
|
|
|
+ // 如果已上传该分片且没有过期
|
|
|
+ if (isset($this->contexts[$blockIndex]) && $this->contexts[$blockIndex]["expiredAt"] >= time()) {
|
|
|
+ $uploaded += $blockSize;
|
|
|
+ fseek($this->inputStream, $blockSize, SEEK_CUR);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
$data = fread($this->inputStream, $blockSize);
|
|
|
if ($data === false) {
|
|
|
throw new \Exception("file read failed", 1);
|
|
|
}
|
|
|
$crc = \Qiniu\crc32_data($data);
|
|
|
$response = $this->makeBlock($data, $blockSize);
|
|
|
+
|
|
|
+
|
|
|
$ret = null;
|
|
|
if ($response->ok() && $response->json() != null) {
|
|
|
$ret = $response->json();
|
|
@@ -91,22 +212,180 @@ final class ResumeUploader
|
|
|
if ($err != null) {
|
|
|
return array(null, $err);
|
|
|
}
|
|
|
-
|
|
|
- $upHostBackup = $this->config->getUpBackupHost($accessKey, $bucket);
|
|
|
+ list($upHostBackup, $err) = $this->config->getUpBackupHostV2($accessKey, $bucket, $this->reqOpt);
|
|
|
+ if ($err != null) {
|
|
|
+ return array(null, $err);
|
|
|
+ }
|
|
|
$this->host = $upHostBackup;
|
|
|
}
|
|
|
+
|
|
|
if ($response->needRetry() || !isset($ret['crc32']) || $crc != $ret['crc32']) {
|
|
|
$response = $this->makeBlock($data, $blockSize);
|
|
|
$ret = $response->json();
|
|
|
}
|
|
|
-
|
|
|
if (!$response->ok() || !isset($ret['crc32']) || $crc != $ret['crc32']) {
|
|
|
return array(null, new Error($this->currentUrl, $response));
|
|
|
}
|
|
|
- array_push($this->contexts, $ret['ctx']);
|
|
|
+
|
|
|
+ // 如果可以在已上传取到说明是过期分片直接修改已上传信息,否则是新的片添加到已上传分片尾部
|
|
|
+ if (isset($this->contexts[$blockIndex])) {
|
|
|
+ $this->contexts[$blockIndex] = array(
|
|
|
+ 'ctx' => $ret['ctx'],
|
|
|
+ 'expiredAt' => $ret['expired_at'],
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ array_push($this->contexts, array(
|
|
|
+ 'ctx' => $ret['ctx'],
|
|
|
+ 'expiredAt' => $ret['expired_at'],
|
|
|
+ ));
|
|
|
+ }
|
|
|
$uploaded += $blockSize;
|
|
|
+
|
|
|
+ // 记录断点
|
|
|
+ if ($this->resumeRecordFile !== null) {
|
|
|
+ $recordData = array(
|
|
|
+ 'contexts' => $this->contexts,
|
|
|
+ 'uploaded' => $uploaded
|
|
|
+ );
|
|
|
+ $recordData = json_encode($recordData);
|
|
|
+
|
|
|
+ if ($recordData) {
|
|
|
+ $isWritten = file_put_contents($this->resumeRecordFile, $recordData);
|
|
|
+ if ($isWritten === false) {
|
|
|
+ error_log("write resumeRecordFile failed");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ error_log('resumeRecordData encode failed');
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- return $this->makeFile($fname);
|
|
|
+
|
|
|
+ // 完成上传
|
|
|
+ list($ret, $err) = $this->makeFile($fname);
|
|
|
+ if ($err !== null) {
|
|
|
+ $response = $err->getResponse();
|
|
|
+ if ($isResumeUpload && $response->statusCode === 701) {
|
|
|
+ fseek($this->inputStream, 0);
|
|
|
+ return $this->uploadV1($fname);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return array($ret, $err);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param string $fname 文件名
|
|
|
+ * @param null|array $blkputRets
|
|
|
+ *
|
|
|
+ * @throws \Exception
|
|
|
+ */
|
|
|
+ private function uploadV2($fname, $blkputRets = null)
|
|
|
+ {
|
|
|
+ $uploaded = 0;
|
|
|
+ $partNumber = 1;
|
|
|
+ $encodedObjectName = $this->key ? \Qiniu\base64_urlSafeEncode($this->key) : '~';
|
|
|
+ $isResumeUpload = $blkputRets !== null;
|
|
|
+
|
|
|
+ // 初始化 upload id
|
|
|
+ $err = null;
|
|
|
+ if ($blkputRets) {
|
|
|
+ if (isset($blkputRets["etags"]) && isset($blkputRets["uploadId"]) &&
|
|
|
+ isset($blkputRets["expiredAt"]) && $blkputRets["expiredAt"] > time() &&
|
|
|
+ $blkputRets["uploaded"] > 0 && is_array($blkputRets["etags"]) &&
|
|
|
+ is_string($blkputRets["uploadId"]) && is_int($blkputRets["expiredAt"])
|
|
|
+ ) {
|
|
|
+ $this->finishedEtags['etags'] = $blkputRets["etags"];
|
|
|
+ $this->finishedEtags["uploadId"] = $blkputRets["uploadId"];
|
|
|
+ $this->finishedEtags["expiredAt"] = $blkputRets["expiredAt"];
|
|
|
+ $this->finishedEtags["uploaded"] = $blkputRets["uploaded"];
|
|
|
+ $uploaded = $blkputRets["uploaded"];
|
|
|
+ $partNumber = count($this->finishedEtags["etags"]) + 1;
|
|
|
+ } else {
|
|
|
+ $err = $this->makeInitReq($encodedObjectName);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ $err = $this->makeInitReq($encodedObjectName);
|
|
|
+ }
|
|
|
+ if ($err != null) {
|
|
|
+ return array(null, $err);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 上传分片
|
|
|
+ fseek($this->inputStream, $uploaded);
|
|
|
+ while ($uploaded < $this->size) {
|
|
|
+ $blockSize = $this->blockSize($uploaded);
|
|
|
+ $data = fread($this->inputStream, $blockSize);
|
|
|
+ if ($data === false) {
|
|
|
+ throw new \Exception("file read failed", 1);
|
|
|
+ }
|
|
|
+ $md5 = md5($data);
|
|
|
+ $response = $this->uploadPart(
|
|
|
+ $data,
|
|
|
+ $partNumber,
|
|
|
+ $this->finishedEtags["uploadId"],
|
|
|
+ $encodedObjectName,
|
|
|
+ $md5
|
|
|
+ );
|
|
|
+
|
|
|
+ $ret = null;
|
|
|
+ if ($response->ok() && $response->json() != null) {
|
|
|
+ $ret = $response->json();
|
|
|
+ }
|
|
|
+ if ($response->statusCode < 0) {
|
|
|
+ list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($this->upToken);
|
|
|
+ if ($err != null) {
|
|
|
+ return array(null, $err);
|
|
|
+ }
|
|
|
+ list($upHostBackup, $err) = $this->config->getUpBackupHostV2($accessKey, $bucket, $this->reqOpt);
|
|
|
+ if ($err != null) {
|
|
|
+ return array(null, $err);
|
|
|
+ }
|
|
|
+ $this->host = $upHostBackup;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($response->needRetry() || !isset($ret['md5']) || $md5 != $ret['md5']) {
|
|
|
+ $response = $this->uploadPart(
|
|
|
+ $data,
|
|
|
+ $partNumber,
|
|
|
+ $this->finishedEtags["uploadId"],
|
|
|
+ $encodedObjectName,
|
|
|
+ $md5
|
|
|
+ );
|
|
|
+ $ret = $response->json();
|
|
|
+ }
|
|
|
+ if ($isResumeUpload && $response->statusCode === 612) {
|
|
|
+ return $this->uploadV2($fname);
|
|
|
+ }
|
|
|
+ if (!$response->ok() || !isset($ret['md5']) || $md5 != $ret['md5']) {
|
|
|
+ return array(null, new Error($this->currentUrl, $response));
|
|
|
+ }
|
|
|
+ $blockStatus = array('etag' => $ret['etag'], 'partNumber' => $partNumber);
|
|
|
+ array_push($this->finishedEtags['etags'], $blockStatus);
|
|
|
+ $partNumber += 1;
|
|
|
+
|
|
|
+ $uploaded += $blockSize;
|
|
|
+ $this->finishedEtags['uploaded'] = $uploaded;
|
|
|
+
|
|
|
+ if ($this->resumeRecordFile !== null) {
|
|
|
+ $recordData = json_encode($this->finishedEtags);
|
|
|
+ if ($recordData) {
|
|
|
+ $isWritten = file_put_contents($this->resumeRecordFile, $recordData);
|
|
|
+ if ($isWritten === false) {
|
|
|
+ error_log("write resumeRecordFile failed");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ error_log('resumeRecordData encode failed');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ list($ret, $err) = $this->completeParts($fname, $this->finishedEtags['uploadId'], $encodedObjectName);
|
|
|
+ if ($err !== null) {
|
|
|
+ $response = $err->getResponse();
|
|
|
+ if ($isResumeUpload && $response->statusCode === 612) {
|
|
|
+ return $this->uploadV2($fname);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return array($ret, $err);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -137,15 +416,25 @@ final class ResumeUploader
|
|
|
|
|
|
/**
|
|
|
* 创建文件
|
|
|
+ *
|
|
|
+ * @param string $fname 文件名
|
|
|
+ * @return array{array | null, Error | null}
|
|
|
*/
|
|
|
private function makeFile($fname)
|
|
|
{
|
|
|
$url = $this->fileUrl($fname);
|
|
|
- $body = implode(',', $this->contexts);
|
|
|
+ $body = implode(',', array_map(function ($ctx) {
|
|
|
+ return $ctx['ctx'];
|
|
|
+ }, $this->contexts));
|
|
|
$response = $this->post($url, $body);
|
|
|
if ($response->needRetry()) {
|
|
|
$response = $this->post($url, $body);
|
|
|
}
|
|
|
+ if ($response->statusCode === 200 || $response->statusCode === 701) {
|
|
|
+ if ($this->resumeRecordFile !== null) {
|
|
|
+ @unlink($this->resumeRecordFile);
|
|
|
+ }
|
|
|
+ }
|
|
|
if (!$response->ok()) {
|
|
|
return array(null, new Error($this->currentUrl, $response));
|
|
|
}
|
|
@@ -156,14 +445,136 @@ final class ResumeUploader
|
|
|
{
|
|
|
$this->currentUrl = $url;
|
|
|
$headers = array('Authorization' => 'UpToken ' . $this->upToken);
|
|
|
- return Client::post($url, $data, $headers);
|
|
|
+ return Client::post($url, $data, $headers, $this->reqOpt);
|
|
|
}
|
|
|
|
|
|
private function blockSize($uploaded)
|
|
|
{
|
|
|
- if ($this->size < $uploaded + Config::BLOCK_SIZE) {
|
|
|
+ if ($this->size < $uploaded + $this->partSize) {
|
|
|
return $this->size - $uploaded;
|
|
|
}
|
|
|
- return Config::BLOCK_SIZE;
|
|
|
+ return $this->partSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function makeInitReq($encodedObjectName)
|
|
|
+ {
|
|
|
+ list($ret, $err) = $this->initReq($encodedObjectName);
|
|
|
+
|
|
|
+ if ($ret == null) {
|
|
|
+ return $err;
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->finishedEtags["uploadId"] = $ret['uploadId'];
|
|
|
+ $this->finishedEtags["expiredAt"] = $ret['expireAt'];
|
|
|
+ return $err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化上传任务
|
|
|
+ */
|
|
|
+ private function initReq($encodedObjectName)
|
|
|
+ {
|
|
|
+ $url = $this->host . '/buckets/' . $this->bucket . '/objects/' . $encodedObjectName . '/uploads';
|
|
|
+ $headers = array(
|
|
|
+ 'Authorization' => 'UpToken ' . $this->upToken,
|
|
|
+ 'Content-Type' => 'application/json'
|
|
|
+ );
|
|
|
+ $response = $this->postWithHeaders($url, null, $headers);
|
|
|
+ $ret = $response->json();
|
|
|
+ if ($response->ok() && $ret != null) {
|
|
|
+ return array($ret, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ return array(null, new Error($url, $response));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分块上传v2
|
|
|
+ */
|
|
|
+ private function uploadPart($block, $partNumber, $uploadId, $encodedObjectName, $md5)
|
|
|
+ {
|
|
|
+ $headers = array(
|
|
|
+ 'Authorization' => 'UpToken ' . $this->upToken,
|
|
|
+ 'Content-Type' => 'application/octet-stream',
|
|
|
+ 'Content-MD5' => $md5
|
|
|
+ );
|
|
|
+ $url = $this->host . '/buckets/' . $this->bucket . '/objects/' . $encodedObjectName .
|
|
|
+ '/uploads/' . $uploadId . '/' . $partNumber;
|
|
|
+ $response = $this->put($url, $block, $headers);
|
|
|
+ if ($response->statusCode === 612) {
|
|
|
+ if ($this->resumeRecordFile !== null) {
|
|
|
+ @unlink($this->resumeRecordFile);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return $response;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 完成分片上传V2
|
|
|
+ *
|
|
|
+ * @param string $fname 文件名
|
|
|
+ * @param int $uploadId 由 {@see initReq} 获取
|
|
|
+ * @param string $encodedObjectName 经过编码的存储路径
|
|
|
+ * @return array{array | null, Error | null}
|
|
|
+ */
|
|
|
+ private function completeParts($fname, $uploadId, $encodedObjectName)
|
|
|
+ {
|
|
|
+ $headers = array(
|
|
|
+ 'Authorization' => 'UpToken ' . $this->upToken,
|
|
|
+ 'Content-Type' => 'application/json'
|
|
|
+ );
|
|
|
+ $etags = $this->finishedEtags['etags'];
|
|
|
+ $sortedEtags = \Qiniu\arraySort($etags, 'partNumber');
|
|
|
+ $metadata = array();
|
|
|
+ $customVars = array();
|
|
|
+ if ($this->params) {
|
|
|
+ foreach ($this->params as $k => $v) {
|
|
|
+ if (strpos($k, 'x:') === 0) {
|
|
|
+ $customVars[$k] = $v;
|
|
|
+ } elseif (strpos($k, 'x-qn-meta-') === 0) {
|
|
|
+ $metadata[$k] = $v;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (empty($metadata)) {
|
|
|
+ $metadata = null;
|
|
|
+ }
|
|
|
+ if (empty($customVars)) {
|
|
|
+ $customVars = null;
|
|
|
+ }
|
|
|
+ $body = array(
|
|
|
+ 'fname' => $fname,
|
|
|
+ 'mimeType' => $this->mime,
|
|
|
+ 'metadata' => $metadata,
|
|
|
+ 'customVars' => $customVars,
|
|
|
+ 'parts' => $sortedEtags
|
|
|
+ );
|
|
|
+ $jsonBody = json_encode($body);
|
|
|
+ $url = $this->host . '/buckets/' . $this->bucket . '/objects/' . $encodedObjectName . '/uploads/' . $uploadId;
|
|
|
+ $response = $this->postWithHeaders($url, $jsonBody, $headers);
|
|
|
+ if ($response->needRetry()) {
|
|
|
+ $response = $this->postWithHeaders($url, $jsonBody, $headers);
|
|
|
+ }
|
|
|
+ if ($response->statusCode === 200 || $response->statusCode === 612) {
|
|
|
+ if ($this->resumeRecordFile !== null) {
|
|
|
+ @unlink($this->resumeRecordFile);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!$response->ok()) {
|
|
|
+ return array(null, new Error($this->currentUrl, $response));
|
|
|
+ }
|
|
|
+ return array($response->json(), null);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function put($url, $data, $headers)
|
|
|
+ {
|
|
|
+ $this->currentUrl = $url;
|
|
|
+ return Client::put($url, $data, $headers, $this->reqOpt);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function postWithHeaders($url, $data, $headers)
|
|
|
+ {
|
|
|
+ $this->currentUrl = $url;
|
|
|
+ return Client::post($url, $data, $headers, $this->reqOpt);
|
|
|
}
|
|
|
}
|