Passed
Pull Request — master (#362)
by
unknown
43:52 queued 18:53
created

ResumeUploader::__construct()   B

Complexity

Conditions 6
Paths 24

Size

Total Lines 37
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 6.0493

Importance

Changes 0
Metric Value
cc 6
eloc 20
c 0
b 0
f 0
nc 24
nop 10
dl 0
loc 37
ccs 24
cts 27
cp 0.8889
crap 6.0493
rs 8.9777

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Qiniu\Storage;
4
5
use Qiniu\Config;
6
use Qiniu\Http\Client;
7
use Qiniu\Http\Error;
8
9
/**
10
 * 断点续上传类, 该类主要实现了断点续上传中的分块上传,
11
 * 以及相应地创建块和创建文件过程.
12
 *
13
 * @link http://developer.qiniu.com/docs/v6/api/reference/up/mkblk.html
14
 * @link http://developer.qiniu.com/docs/v6/api/reference/up/mkfile.html
15
 */
16
final class ResumeUploader
17
{
18
    private $upToken;
19
    private $key;
20
    private $inputStream;
21
    private $size;
22
    private $params;
23
    private $mime;
24
    private $contexts;
25
    private $finishedEtags;
26
    private $host;
27
    private $bucket;
28
    private $currentUrl;
29
    private $config;
30
    private $resumeRecordFile;
31
    private $version;
32
    private $partSize;
33
34
    /**
35
     * 上传二进制流到七牛
36
     *
37
     * @param string $upToken 上传凭证
38
     * @param string $key 上传文件名
39
     * @param string $inputStream 上传二进制流
40 6
     * @param string $size 上传流的大小
41
     * @param string $params 自定义变量
42
     * @param string $mime 上传数据的mimeType
43
     * @param string $config
44
     * @param string $resumeRecordFile 断点续传的已上传的部分信息记录文件
45
     * @param string $version 分片上传版本 目前支持v1/v2版本 默认v1
46
     * @param string $partSize 分片上传v2字段 默认大小为4MB 分片大小范围为1 MB - 1 GB
47
     *
48
     * @link http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
49
     */
50 6
    public function __construct(
51 6
        $upToken,
52 6
        $key,
53 6
        $inputStream,
54 6
        $size,
55 6
        $params,
56 6
        $mime,
57 6
        $config,
58
        $resumeRecordFile = null,
59 6
        $version = 'v1',
60 6
        $partSize = config::BLOCK_SIZE
61
    ) {
62
63
        $this->upToken = $upToken;
64 6
        $this->key = $key;
65 6
        $this->inputStream = $inputStream;
66
        $this->size = $size;
67
        $this->params = $params;
68 6
        $this->mime = $mime;
69 6
        $this->contexts = array();
70
        $this->finishedEtags = array("etags"=>array(), "uploadId"=>"", "expiredAt"=>0, "uploaded"=>0);
71
        $this->config = $config;
72
        $this->resumeRecordFile = $resumeRecordFile ? $resumeRecordFile : null;
73
        $this->version = $version ? $version : 'v1';
74 6
        $this->partSize = $partSize ? $partSize : config::BLOCK_SIZE;
75
76 6
        list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken);
77 6
        $this->bucket = $bucket;
78 6
        if ($err != null) {
79 6
            return array(null, $err);
80 6
        }
81
82
        $upHost = $config->getUpHost($accessKey, $bucket);
83 6
        if ($err != null) {
84 6
            throw new \Exception($err->message(), 1);
85 6
        }
86 6
        $this->host = $upHost;
87 6
    }
88 6
89 6
    /**
90 3
     * 上传操作
91 3
     */
92
    public function upload($fname)
93
    {
94
        $uploaded = 0;
95 3
        if ($this->version == 'v2') {
96 3
            $partNumber = 1;
97 3
            $encodedObjectName = $this->key? \Qiniu\base64_urlSafeEncode($this->key) : '~';
98 6
        };
99 3
        // get upload record from resumeRecordFile
100 3
        if ($this->resumeRecordFile != null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $this->resumeRecordFile of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
101 3
            $blkputRets = null;
102
            if (file_exists($this->resumeRecordFile)) {
103 6
                $stream = fopen($this->resumeRecordFile, 'r');
104
                if ($stream) {
0 ignored issues
show
introduced by
$stream is of type resource, thus it always evaluated to false.
Loading history...
105
                    $streamLen = filesize($this->resumeRecordFile);
106 6
                    if ($streamLen > 0) {
107 6
                        $contents = fread($stream, $streamLen);
108 6
                        fclose($stream);
109 6
                        if ($contents) {
110
                            $blkputRets = json_decode($contents, true);
111
                            if ($blkputRets === null) {
112
                                error_log("resumeFile contents decode error");
113
                            }
114
                        } else {
115 6
                            error_log("read resumeFile failed");
116
                        }
117 6
                    } else {
118 6
                        error_log("resumeFile is empty");
119
                    }
120
                } else {
121 6
                    error_log("resumeFile open failed");
122
                }
123 6
            } else {
124 6
                error_log("resumeFile not exists");
125 6
            }
126 6
127 6
            if ($blkputRets) {
0 ignored issues
show
introduced by
$blkputRets is of type null, thus it always evaluated to false.
Loading history...
128 6
                if ($this->version == 'v1') {
129 6
                    if (isset($blkputRets['contexts']) && isset($blkputRets['uploaded']) &&
130
                        is_array($blkputRets['contexts']) && is_int($blkputRets['uploaded'])) {
131
                        $this->contexts = $blkputRets['contexts'];
132
                        $uploaded = $blkputRets['uploaded'];
133
                    }
134
                } elseif ($this->version == 'v2') {
135 6
                    if (isset($blkputRets["etags"]) && isset($blkputRets["uploadId"]) &&
136
                        isset($blkputRets["expiredAt"]) && $blkputRets["expiredAt"] > time()
137
                        && $blkputRets["uploaded"] > 0 && is_array($blkputRets["etags"]) &&
138
                        is_string($blkputRets["uploadId"]) && is_int($blkputRets["expiredAt"])) {
139
                        $this->finishedEtags['etags'] = $blkputRets["etags"];
140
                        $this->finishedEtags["uploadId"] = $blkputRets["uploadId"];
141 6
                        $this->finishedEtags["expiredAt"] = $blkputRets["expiredAt"];
142
                        $this->finishedEtags["uploaded"] = $blkputRets["uploaded"];
143 6
                        $uploaded = $blkputRets["uploaded"];
144 6
                        $partNumber = count($this->finishedEtags["etags"]) + 1;
145 6
                    } else {
146 6
                        $this->makeInitReq($encodedObjectName);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $encodedObjectName does not seem to be defined for all execution paths leading up to this point.
Loading history...
147
                    }
148
                } else {
149 6
                    throw new \Exception("only support v1/v2 now!");
150
                }
151
            } else {
152 6
                if ($this->version == 'v2') {
153
                    $this->makeInitReq($encodedObjectName);
154
                }
155 6
            }
156
        } else {
157 6
            // init a Multipart Upload task if choose v2
158 6
            if ($this->version == 'v2') {
159 6
                $this->makeInitReq($encodedObjectName);
160
            }
161
        }
162 6
163
        while ($uploaded < $this->size) {
164 6
            $blockSize = $this->blockSize($uploaded);
165 6
            $data = fread($this->inputStream, $blockSize);
0 ignored issues
show
Bug introduced by
$this->inputStream of type string is incompatible with the type resource expected by parameter $stream of fread(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

165
            $data = fread(/** @scrutinizer ignore-type */ $this->inputStream, $blockSize);
Loading history...
Bug introduced by
It seems like $blockSize can also be of type string; however, parameter $length of fread() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

165
            $data = fread($this->inputStream, /** @scrutinizer ignore-type */ $blockSize);
Loading history...
166
            if ($data === false) {
167 6
                throw new \Exception("file read failed", 1);
168
            }
169
            if ($this->version == 'v1') {
170
                $crc = \Qiniu\crc32_data($data);
171
                $response = $this->makeBlock($data, $blockSize);
172
            } else {
173
                $md5 = md5($data);
174
                $response = $this->uploadPart(
175
                    $data,
176
                    $partNumber,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $partNumber does not seem to be defined for all execution paths leading up to this point.
Loading history...
177
                    $this->finishedEtags["uploadId"],
178
                    $encodedObjectName,
179
                    $md5
180
                );
181
            }
182
183
            $ret = null;
184
            if ($response->ok() && $response->json() != null) {
185
                $ret = $response->json();
186
            }
187
            if ($response->statusCode < 0) {
188
                list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($this->upToken);
189
                if ($err != null) {
190
                    return array(null, $err);
191
                }
192
                $upHostBackup = $this->config->getUpBackupHost($accessKey, $bucket);
193
                $this->host = $upHostBackup;
194
            }
195
196
            if ($this->version == 'v1') {
197
                if ($response->needRetry() || !isset($ret['crc32']) || $crc != $ret['crc32']) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $crc does not seem to be defined for all execution paths leading up to this point.
Loading history...
198
                    $response = $this->makeBlock($data, $blockSize);
199
                    $ret = $response->json();
200
                }
201
202
                if (!$response->ok() || !isset($ret['crc32']) || $crc != $ret['crc32']) {
203
                    return array(null, new Error($this->currentUrl, $response));
204
                }
205
                array_push($this->contexts, $ret['ctx']);
206
            } else {
207
                if ($response->needRetry() || !isset($ret['md5']) || $md5 != $ret['md5']) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $md5 does not seem to be defined for all execution paths leading up to this point.
Loading history...
208
                    $response = $this->uploadPart(
209
                        $data,
210
                        $partNumber,
211
                        $this->finishedEtags["uploadId"],
212
                        $encodedObjectName,
213
                        $md5
214
                    );
215
                    $ret = $response->json();
216
                }
217
218
                if (!$response->ok() || !isset($ret['md5']) || $md5 != $ret['md5']) {
219
                    return array(null, new Error($this->currentUrl, $response));
220
                }
221
                $blockStatus = array('etag' => $ret['etag'], 'partNumber' => $partNumber);
222
                array_push($this->finishedEtags['etags'], $blockStatus);
223
                $partNumber += 1;
224
            }
225
226
            $uploaded += $blockSize;
227
            if ($this->version == 'v2') {
228
                $this->finishedEtags['uploaded'] = $uploaded;
229
            }
230
231
            if ($this->resumeRecordFile !== null) {
232
                if ($this->version == 'v1') {
233
                    $recordData = array(
234
                        'contexts' => $this->contexts,
235
                        'uploaded' => $uploaded
236
                    );
237
                    $recordData = json_encode($recordData);
238
                } else {
239
                    $recordData = json_encode($this->finishedEtags);
240
                }
241
                if ($recordData) {
242
                    $isWritten = file_put_contents($this->resumeRecordFile, $recordData);
243
                    if ($isWritten === false) {
244
                        error_log("write resumeRecordFile failed");
245
                    }
246
                } else {
247
                    error_log('resumeRecordData encode failed');
248
                }
249
            }
250
        }
251
        if ($this->version == 'v1') {
252
            return $this->makeFile($fname);
253
        } else {
254
            return $this->completeParts($fname, $this->finishedEtags['uploadId'], $encodedObjectName);
255
        }
256
    }
257
258
    /**
259
     * 创建块
260
     */
261
    private function makeBlock($block, $blockSize)
262
    {
263
        $url = $this->host . '/mkblk/' . $blockSize;
264
        return $this->post($url, $block);
265
    }
266
267
    private function fileUrl($fname)
268
    {
269
        $url = $this->host . '/mkfile/' . $this->size;
270
        $url .= '/mimeType/' . \Qiniu\base64_urlSafeEncode($this->mime);
271
        if ($this->key != null) {
272
            $url .= '/key/' . \Qiniu\base64_urlSafeEncode($this->key);
273
        }
274
        $url .= '/fname/' . \Qiniu\base64_urlSafeEncode($fname);
275
        if (!empty($this->params)) {
276
            foreach ($this->params as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $this->params of type string is not traversable.
Loading history...
277
                $val = \Qiniu\base64_urlSafeEncode($value);
278
                $url .= "/$key/$val";
279
            }
280
        }
281
        return $url;
282
    }
283
284
    /**
285
     * 创建文件
286
     */
287
    private function makeFile($fname)
288
    {
289
        $url = $this->fileUrl($fname);
290
        $body = implode(',', $this->contexts);
291
        $response = $this->post($url, $body);
292
        if ($response->needRetry()) {
293
            $response = $this->post($url, $body);
294
        }
295
        if (!$response->ok()) {
296
            return array(null, new Error($this->currentUrl, $response));
297
        }
298
        return array($response->json(), null);
299
    }
300
301
    private function post($url, $data)
302
    {
303
        $this->currentUrl = $url;
304
        $headers = array('Authorization' => 'UpToken ' . $this->upToken);
305
        return Client::post($url, $data, $headers);
306
    }
307
308
    private function blockSize($uploaded)
309
    {
310
        if ($this->size < $uploaded + $this->partSize) {
311
            return $this->size - $uploaded;
312
        }
313
        return $this->partSize;
314
    }
315
316
    private function makeInitReq($encodedObjectName)
317
    {
318
        $res = $this->initReq($encodedObjectName);
319
        $this->finishedEtags["uploadId"] = $res['uploadId'];
320
        $this->finishedEtags["expiredAt"] = $res['expireAt'];
321
    }
322
323
    /**
324
     * 初始化上传任务
325
     */
326
    private function initReq($encodedObjectName)
327
    {
328
        $url = $this->host.'/buckets/'.$this->bucket.'/objects/'.$encodedObjectName.'/uploads';
329
        $headers = array(
330
            'Authorization' => 'UpToken ' . $this->upToken,
331
            'Content-Type' => 'application/json'
332
        );
333
        $response = $this->postWithHeaders($url, null, $headers);
334
        return $response->json();
335
    }
336
337
    /**
338
     * 分块上传v2
339
     */
340
    private function uploadPart($block, $partNumber, $uploadId, $encodedObjectName, $md5)
341
    {
342
        $headers = array(
343
            'Authorization' => 'UpToken ' . $this->upToken,
344
            'Content-Type' => 'application/octet-stream',
345
            'Content-MD5' => $md5
346
        );
347
        $url = $this->host.'/buckets/'.$this->bucket.'/objects/'.$encodedObjectName.
348
            '/uploads/'.$uploadId.'/'.$partNumber;
349
        $response = $this->put($url, $block, $headers);
350
        return $response;
351
    }
352
353
    private function completeParts($fname, $uploadId, $encodedObjectName)
354
    {
355
        $headers = array(
356
            'Authorization' => 'UpToken '.$this->upToken,
357
            'Content-Type' => 'application/json'
358
        );
359
        $etags = $this->finishedEtags['etags'];
360
        $sortedEtags = \Qiniu\arraySort($etags, 'partNumber');
361
        $metadata = array();
362
        $customVars = array();
363
        if ($this->params) {
364
            foreach ($this->params as $k => $v) {
0 ignored issues
show
Bug introduced by
The expression $this->params of type string is not traversable.
Loading history...
365
                if (strpos($k, 'x:') === 0) {
366
                    $customVars[$k] = $v;
367
                } elseif (strpos($k, 'x-qn-meta-') === 0) {
368
                    $metadata[$k] = $v;
369
                }
370
            }
371
        }
372
        if (empty($metadata)) {
373
            $metadata = null;
374
        }
375
        if (empty($customVars)) {
376
            $customVars = null;
377
        }
378
        $body = array(
379
            'fname' => $fname,
380
            'mimeType' => $this->mime,
381
            'metadata' => $metadata,
382
            'customVars' => $customVars,
383
            'parts' => $sortedEtags
384
        );
385
        $jsonBody = json_encode($body);
386
        $url = $this->host.'/buckets/'.$this->bucket.'/objects/'.$encodedObjectName.'/uploads/'.$uploadId;
387
        $response = $this->postWithHeaders($url, $jsonBody, $headers);
388
        if ($response->needRetry()) {
389
            $response = $this->postWithHeaders($url, $jsonBody, $headers);
390
        }
391
        if (!$response->ok()) {
392
            return array(null, new Error($this->currentUrl, $response));
393
        }
394
        return array($response->json(), null);
395
    }
396
397
    private function put($url, $data, $headers)
398
    {
399
        $this->currentUrl = $url;
400
        return Client::put($url, $data, $headers);
401
    }
402
403
    private function postWithHeaders($url, $data, $headers)
404
    {
405
        $this->currentUrl = $url;
406
        return Client::post($url, $data, $headers);
407
    }
408
}
409