Passed
Pull Request — master (#355)
by
unknown
21:49
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 === false) {
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
                        }
149 6
                    } else {
150
                        $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...
151
                    }
152 6
                } else {
153
                    throw new \Exception("only support v1/v2 now!");
154
                }
155 6
            } else {
156
                if ($this->version == 'v2') {
157 6
                    $this->makeInitReq($encodedObjectName);
158 6
                }
159 6
            }
160
        } else {
161
            // init a Multipart Upload task if choose v2
162 6
            if ($this->version == 'v2') {
163
                $this->makeInitReq($encodedObjectName);
164 6
            }
165 6
        }
166
167 6
        while ($uploaded < $this->size) {
168
            $blockSize = $this->blockSize($uploaded);
169
            $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

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

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