Passed
Pull Request — master (#355)
by
unknown
22:22
created

ResumeUploader::__construct()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 37
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 5.0342

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 20
c 1
b 0
f 0
nc 12
nop 10
dl 0
loc 37
ccs 24
cts 27
cp 0.8889
crap 5.0342
rs 9.2888

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,
60 6
        $version,
61
        $partSize
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;
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) {
102
            $blkputRets = null;
103 6
            if (file_exists($this->resumeRecordFile)) {
104
                $stream = fopen($this->resumeRecordFile, 'r');
105
                if ($stream && filesize($this->resumeRecordFile) > 0) {
0 ignored issues
show
introduced by
$stream is of type resource, thus it always evaluated to false.
Loading history...
106 6
                    $contents = fread($stream, filesize($this->resumeRecordFile));
107 6
                    fclose($stream);
108 6
                    $blkputRets = json_decode($contents, true);
109 6
                } else {
110
                    error_log("resumeFile open failed or resumeFile is empty");
111
                }
112
            } else {
113
                error_log("resumeFile not exists");
114
            }
115 6
116
            if ($blkputRets) {
0 ignored issues
show
introduced by
$blkputRets is of type null, thus it always evaluated to false.
Loading history...
117 6
                if ($this->version == 'v1') {
118 6
                    if (isset($blkputRets['contexts']) && isset($blkputRets['uploaded'])) {
119
                        $this->contexts = $blkputRets['contexts'];
120
                        $uploaded = $blkputRets['uploaded'];
121 6
                    }
122
                } else if ($this->version == 'v2') {
123 6
                    if (isset($blkputRets["etags"]) && isset($blkputRets["uploadId"]) &&
124 6
                        isset($blkputRets["expiredAt"]) && $blkputRets["expiredAt"] > time()
125 6
                        && $blkputRets["uploaded"] > 0) {
126 6
                            $this->finishedEtags['etags'] = $blkputRets["etags"];
127 6
                            $this->finishedEtags["uploadId"] = $blkputRets["uploadId"];
128 6
                            $this->finishedEtags["expiredAt"] = $blkputRets["expiredAt"];
129 6
                            $this->finishedEtags["uploaded"] = $blkputRets["uploaded"];
130
                            $uploaded = $blkputRets["uploaded"];
131
                            $partNumber = count($this->finishedEtags["etags"]) + 1;
132
                    } else {
133
                        $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...
134
                    }
135 6
                } else {
136
                    throw new \Exception("only support v1/v2 now!");
137
                }
138
            } else {
139
                if ($this->version == 'v2') {
140
                    $this->makeInitReq($encodedObjectName);
141 6
                }
142
            }
143 6
        } else {
144 6
            // init a Multipart Upload task if choose v2
145 6
            if ($this->version == 'v2') {
146 6
                $this->makeInitReq($encodedObjectName);
147
            }
148
        }
149 6
150
        while ($uploaded < $this->size) {
151
            $blockSize = $this->blockSize($uploaded);
152 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

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

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