UploadContent::shouldUploadChunk()   A
last analyzed

Complexity

Conditions 4
Paths 8

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 2
c 1
b 0
f 0
nc 8
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 4
rs 10
1
<?php
2
3
namespace Srmklive\Dropbox;
4
5
trait UploadContent
6
{
7
    /**
8
     * The file should be uploaded in chunks if it size exceeds the 150 MB threshold
9
     * or if the resource size could not be determined (eg. a popen() stream).
10
     *
11
     * @param string|resource $contents
12
     *
13
     * @return bool
14
     */
15 1
    protected function shouldUploadChunk($contents)
16
    {
17 1
        $size = is_string($contents) ? strlen($contents) : fstat($contents)['size'];
18
19 1
        return ($this->isPipe($contents) || ($size === null)) ? true : ($size > $this->maxChunkSize);
20
    }
21
22
    /**
23
     * Check if the contents is a pipe stream (not seekable, no size defined).
24
     *
25
     * @param string|resource $contents
26
     *
27
     * @return bool
28
     */
29 1
    protected function isPipe($contents)
30
    {
31 1
        return is_resource($contents) ? (fstat($contents)['mode'] & 010000) != 0 : false;
32
    }
33
34
    /**
35
     * Upload file split in chunks. This allows uploading large files, since
36
     * Dropbox API v2 limits the content size to 150MB.
37
     *
38
     * The chunk size will affect directly the memory usage, so be careful.
39
     * Large chunks tends to speed up the upload, while smaller optimizes memory usage.
40
     *
41
     * @param string          $path
42
     * @param string|resource $contents
43
     * @param string          $mode
44
     * @param int             $chunkSize
45
     *
46
     * @throws \Exception
47
     *
48
     * @return array
49
     */
50 3
    public function uploadChunk($path, $contents, $mode = 'add', $chunkSize = null)
51
    {
52 3
        $chunkSize = empty($chunkSize) ? static::MAX_CHUNK_SIZE : $chunkSize;
0 ignored issues
show
Bug introduced by
The constant Srmklive\Dropbox\UploadContent::MAX_CHUNK_SIZE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
53 3
        $stream = $contents;
54
55
        // This method relies on resources, so we need to convert strings to resource
56 3
        if (is_string($contents)) {
57 1
            $stream = fopen('php://memory', 'r+');
58 1
            fwrite($stream, $contents);
59 1
            rewind($stream);
60
        }
61
62 3
        $data = self::readChunk($stream, $chunkSize);
0 ignored issues
show
Bug introduced by
It seems like $stream can also be of type string; however, parameter $stream of Srmklive\Dropbox\UploadContent::readChunk() does only seem to accept resource, 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

62
        $data = self::readChunk(/** @scrutinizer ignore-type */ $stream, $chunkSize);
Loading history...
63 3
        $cursor = null;
64
65 3
        while (!((strlen($data) < $chunkSize) || feof($stream))) {
0 ignored issues
show
Bug introduced by
It seems like $stream can also be of type string; however, parameter $stream of feof() does only seem to accept resource, 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

65
        while (!((strlen($data) < $chunkSize) || feof(/** @scrutinizer ignore-type */ $stream))) {
Loading history...
66
            // Start upload session on first iteration, then just append on subsequent iterations
67 2
            $cursor = isset($cursor) ? $this->appendContentToUploadSession($data, $cursor) : $this->startUploadSession($data);
68 2
            $data = self::readChunk($stream, $chunkSize);
69
        }
70
71
        // If there's no cursor here, our stream is small enough to a single request
72 3
        if (!isset($cursor)) {
73 1
            $cursor = $this->startUploadSession($data);
74 1
            $data = '';
75
        }
76
77 3
        return $this->finishUploadSession($data, $cursor, $path, $mode);
78
    }
79
80
    /**
81
     * Upload sessions allow you to upload a single file in one or more requests,
82
     * for example where the size of the file is greater than 150 MB.
83
     * This call starts a new upload session with the given data.
84
     *
85
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-start
86
     *
87
     * @param string $contents
88
     * @param bool   $close
89
     *
90
     * @return \Srmklive\Dropbox\DropboxUploadCounter
91
     */
92 1
    public function startUploadSession($contents, $close = false)
93
    {
94 1
        $this->setupRequest(
0 ignored issues
show
Bug introduced by
It seems like setupRequest() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

94
        $this->/** @scrutinizer ignore-call */ 
95
               setupRequest(
Loading history...
95 1
            compact('close')
96
        );
97
98 1
        $this->apiEndpoint = 'files/upload_session/start';
0 ignored issues
show
Bug Best Practice introduced by
The property apiEndpoint does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
99
100 1
        $this->content = $contents;
0 ignored issues
show
Bug Best Practice introduced by
The property content does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
101
102 1
        $response = json_decode(
103 1
            $this->doDropboxApiContentRequest()->getBody(),
0 ignored issues
show
Bug introduced by
It seems like doDropboxApiContentRequest() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

103
            $this->/** @scrutinizer ignore-call */ 
104
                   doDropboxApiContentRequest()->getBody(),
Loading history...
104 1
            true
105
        );
106
107 1
        return new DropboxUploadCounter($response['session_id'], strlen($contents));
108
    }
109
110
    /**
111
     * Append more data to an upload session.
112
     * When the parameter close is set, this call will close the session.
113
     * A single request should not upload more than 150 MB.
114
     *
115
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-append_v2
116
     *
117
     * @param string               $contents
118
     * @param DropboxUploadCounter $cursor
119
     * @param bool                 $close
120
     *
121
     * @return \Srmklive\Dropbox\DropboxUploadCounter
122
     */
123 1
    public function appendContentToUploadSession($contents, DropboxUploadCounter $cursor, $close = false)
124
    {
125 1
        $this->setupRequest(compact('cursor', 'close'));
126
127 1
        $this->apiEndpoint = 'files/upload_session/append_v2';
0 ignored issues
show
Bug Best Practice introduced by
The property apiEndpoint does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
128
129 1
        $this->content = $contents;
0 ignored issues
show
Bug Best Practice introduced by
The property content does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
130
131 1
        $this->doDropboxApiContentRequest()->getBody();
132
133 1
        $cursor->offset += strlen($contents);
134
135 1
        return $cursor;
136
    }
137
138
    /**
139
     * Finish an upload session and save the uploaded data to the given file path.
140
     * A single request should not upload more than 150 MB.
141
     *
142
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-finish
143
     *
144
     * @param string                                 $contents
145
     * @param \Srmklive\Dropbox\DropboxUploadCounter $cursor
146
     * @param string                                 $path
147
     * @param string|array                           $mode
148
     * @param bool                                   $autorename
149
     * @param bool                                   $mute
150
     *
151
     * @return array
152
     */
153 1
    public function finishUploadSession($contents, DropboxUploadCounter $cursor, $path, $mode = 'add', $autorename = false, $mute = false)
154
    {
155 1
        $arguments = compact('cursor');
156 1
        $arguments['commit'] = compact('path', 'mode', 'autorename', 'mute');
157
158 1
        $this->setupRequest($arguments);
159
160 1
        $this->apiEndpoint = 'files/upload_session/finish';
0 ignored issues
show
Bug Best Practice introduced by
The property apiEndpoint does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
161
162 1
        $this->content = $contents;
0 ignored issues
show
Bug Best Practice introduced by
The property content does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
163
164 1
        $response = $this->doDropboxApiContentRequest();
165
166 1
        $metadata = json_decode($response->getBody(), true);
167
168 1
        $metadata['.tag'] = 'file';
169
170 1
        return $metadata;
171
    }
172
173
    /**
174
     * Sometimes fread() returns less than the request number of bytes (for example, when reading
175
     * from network streams).  This function repeatedly calls fread until the requested number of
176
     * bytes have been read or we've reached EOF.
177
     *
178
     * @param resource $stream
179
     * @param int      $chunkSize
180
     *
181
     * @throws \Exception
182
     *
183
     * @return string
184
     */
185 3
    protected static function readChunk($stream, $chunkSize)
186
    {
187 3
        $chunk = '';
188 3
        while (!feof($stream) && $chunkSize > 0) {
189 3
            $part = fread($stream, $chunkSize);
190
191 3
            if ($part === false) {
192
                throw new \Exception('Error reading from $stream.');
193
            }
194
195 3
            $chunk .= $part;
196 3
            $chunkSize -= strlen($part);
197
        }
198
199 3
        return $chunk;
200
    }
201
}
202