Passed
Pull Request — master (#355)
by
unknown
20:42
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
                        if (is_array($blkputRets['contexts']) && is_int($blkputRets['uploaded'])) {
132
                            $this->contexts = $blkputRets['contexts'];
133
                            $uploaded = $blkputRets['uploaded'];
134
                        }
135 6
                    }
136
                } else if ($this->version == 'v2') {
137
                    if (isset($blkputRets["etags"]) && isset($blkputRets["uploadId"]) &&
138
                        isset($blkputRets["expiredAt"]) && $blkputRets["expiredAt"] > time()
139
                        && $blkputRets["uploaded"] > 0) {
140
                        if (is_array($blkputRets["etags"]) && is_string($blkputRets["uploadId"]) &&
141 6
                        is_int($blkputRets["expiredAt"])) {
142
                            $this->finishedEtags['etags'] = $blkputRets["etags"];
143 6
                            $this->finishedEtags["uploadId"] = $blkputRets["uploadId"];
144 6
                            $this->finishedEtags["expiredAt"] = $blkputRets["expiredAt"];
145 6
                            $this->finishedEtags["uploaded"] = $blkputRets["uploaded"];
146 6
                            $uploaded = $blkputRets["uploaded"];
147
                            $partNumber = count($this->finishedEtags["etags"]) + 1;
148
                        } else {
149 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...
150
                        }
151
                    } else {
152 6
                        $this->makeInitReq($encodedObjectName);
153
                    }
154
                } else {
155 6
                    throw new \Exception("only support v1/v2 now!");
156
                }
157 6
            } else {
158 6
                if ($this->version == 'v2') {
159 6
                    $this->makeInitReq($encodedObjectName);
160
                }
161
            }
162 6
        } else {
163
            // init a Multipart Upload task if choose v2
164 6
            if ($this->version == 'v2') {
165 6
                $this->makeInitReq($encodedObjectName);
166
            }
167 6
        }
168
169
        while ($uploaded < $this->size) {
170
            $blockSize = $this->blockSize($uploaded);
171
            $data = fread($this->inputStream, $blockSize);
0 ignored issues
show
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

171
            $data = fread($this->inputStream, /** @scrutinizer ignore-type */ $blockSize);
Loading history...
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

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