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

166
            $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

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