source: vendor/google/apiclient/src/Http/MediaFileUpload.php

Last change on this file was e3d4e0a, checked in by Vlado 222039 <vlado.popovski@…>, 7 days ago

Upload project files

  • Property mode set to 100644
File size: 9.9 KB
RevLine 
[e3d4e0a]1<?php
2/**
3 * Copyright 2012 Google Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18namespace Google\Http;
19
20use Google\Client;
21use Google\Exception as GoogleException;
22use GuzzleHttp\Psr7;
23use GuzzleHttp\Psr7\Request;
24use GuzzleHttp\Psr7\Uri;
25use Psr\Http\Message\RequestInterface;
26
27/**
28 * Manage large file uploads, which may be media but can be any type
29 * of sizable data.
30 */
31class MediaFileUpload
32{
33 const UPLOAD_MEDIA_TYPE = 'media';
34 const UPLOAD_MULTIPART_TYPE = 'multipart';
35 const UPLOAD_RESUMABLE_TYPE = 'resumable';
36
37 /** @var string $mimeType */
38 private $mimeType;
39
40 /** @var string $data */
41 private $data;
42
43 /** @var bool $resumable */
44 private $resumable;
45
46 /** @var int $chunkSize */
47 private $chunkSize;
48
49 /** @var int $size */
50 private $size;
51
52 /** @var string $resumeUri */
53 private $resumeUri;
54
55 /** @var int $progress */
56 private $progress;
57
58 /** @var Client */
59 private $client;
60
61 /** @var RequestInterface */
62 private $request;
63
64 /** @var string */
65 private $boundary; // @phpstan-ignore-line
66
67 /**
68 * Result code from last HTTP call
69 * @var int
70 */
71 private $httpResultCode;
72
73 /**
74 * @param Client $client
75 * @param RequestInterface $request
76 * @param string $mimeType
77 * @param string $data The bytes you want to upload.
78 * @param bool $resumable
79 * @param int $chunkSize File will be uploaded in chunks of this many bytes.
80 * only used if resumable=True
81 */
82 public function __construct(
83 Client $client,
84 RequestInterface $request,
85 $mimeType,
86 $data,
87 $resumable = false,
88 $chunkSize = 0
89 ) {
90 $this->client = $client;
91 $this->request = $request;
92 $this->mimeType = $mimeType;
93 $this->data = $data;
94 $this->resumable = $resumable;
95 $this->chunkSize = $chunkSize;
96 $this->progress = 0;
97
98 $this->process();
99 }
100
101 /**
102 * Set the size of the file that is being uploaded.
103 * @param int $size - int file size in bytes
104 */
105 public function setFileSize($size)
106 {
107 $this->size = $size;
108 }
109
110 /**
111 * Return the progress on the upload
112 * @return int progress in bytes uploaded.
113 */
114 public function getProgress()
115 {
116 return $this->progress;
117 }
118
119 /**
120 * Send the next part of the file to upload.
121 * @param string|bool $chunk Optional. The next set of bytes to send. If false will
122 * use $data passed at construct time.
123 */
124 public function nextChunk($chunk = false)
125 {
126 $resumeUri = $this->getResumeUri();
127
128 if (false == $chunk) {
129 $chunk = substr($this->data, $this->progress, $this->chunkSize);
130 }
131
132 $lastBytePos = $this->progress + strlen($chunk) - 1;
133 $headers = [
134 'content-range' => "bytes $this->progress-$lastBytePos/$this->size",
135 'content-length' => (string) strlen($chunk),
136 'expect' => '',
137 ];
138
139 $request = new Request(
140 'PUT',
141 $resumeUri,
142 $headers,
143 Psr7\Utils::streamFor($chunk)
144 );
145
146 return $this->makePutRequest($request);
147 }
148
149 /**
150 * Return the HTTP result code from the last call made.
151 * @return int code
152 */
153 public function getHttpResultCode()
154 {
155 return $this->httpResultCode;
156 }
157
158 /**
159 * Sends a PUT-Request to google drive and parses the response,
160 * setting the appropiate variables from the response()
161 *
162 * @param RequestInterface $request the Request which will be send
163 *
164 * @return false|mixed false when the upload is unfinished or the decoded http response
165 *
166 */
167 private function makePutRequest(RequestInterface $request)
168 {
169 $response = $this->client->execute($request);
170 $this->httpResultCode = $response->getStatusCode();
171
172 if (308 == $this->httpResultCode) {
173 // Track the amount uploaded.
174 $range = $response->getHeaderLine('range');
175 if ($range) {
176 $range_array = explode('-', $range);
177 $this->progress = ((int) $range_array[1]) + 1;
178 }
179
180 // Allow for changing upload URLs.
181 $location = $response->getHeaderLine('location');
182 if ($location) {
183 $this->resumeUri = $location;
184 }
185
186 // No problems, but upload not complete.
187 return false;
188 }
189
190 return REST::decodeHttpResponse($response, $this->request);
191 }
192
193 /**
194 * Resume a previously unfinished upload
195 * @param string $resumeUri the resume-URI of the unfinished, resumable upload.
196 */
197 public function resume($resumeUri)
198 {
199 $this->resumeUri = $resumeUri;
200 $headers = [
201 'content-range' => "bytes */$this->size",
202 'content-length' => '0',
203 ];
204 $httpRequest = new Request(
205 'PUT',
206 $this->resumeUri,
207 $headers
208 );
209 return $this->makePutRequest($httpRequest);
210 }
211
212 /**
213 * @return RequestInterface
214 * @visible for testing
215 */
216 private function process()
217 {
218 $this->transformToUploadUrl();
219 $request = $this->request;
220
221 $postBody = '';
222 $contentType = false;
223
224 $meta = json_decode((string) $request->getBody(), true);
225
226 $uploadType = $this->getUploadType($meta);
227 $request = $request->withUri(
228 Uri::withQueryValue($request->getUri(), 'uploadType', $uploadType)
229 );
230
231 $mimeType = $this->mimeType ?: $request->getHeaderLine('content-type');
232
233 if (self::UPLOAD_RESUMABLE_TYPE == $uploadType) {
234 $contentType = $mimeType;
235 $postBody = is_string($meta) ? $meta : json_encode($meta);
236 } elseif (self::UPLOAD_MEDIA_TYPE == $uploadType) {
237 $contentType = $mimeType;
238 $postBody = $this->data;
239 } elseif (self::UPLOAD_MULTIPART_TYPE == $uploadType) {
240 // This is a multipart/related upload.
241 $boundary = $this->boundary ?: mt_rand();
242 $boundary = str_replace('"', '', $boundary);
243 $contentType = 'multipart/related; boundary=' . $boundary;
244 $related = "--$boundary\r\n";
245 $related .= "Content-Type: application/json; charset=UTF-8\r\n";
246 $related .= "\r\n" . json_encode($meta) . "\r\n";
247 $related .= "--$boundary\r\n";
248 $related .= "Content-Type: $mimeType\r\n";
249 $related .= "Content-Transfer-Encoding: base64\r\n";
250 $related .= "\r\n" . base64_encode($this->data) . "\r\n";
251 $related .= "--$boundary--";
252 $postBody = $related;
253 }
254
255 $request = $request->withBody(Psr7\Utils::streamFor($postBody));
256
257 if ($contentType) {
258 $request = $request->withHeader('content-type', $contentType);
259 }
260
261 return $this->request = $request;
262 }
263
264 /**
265 * Valid upload types:
266 * - resumable (UPLOAD_RESUMABLE_TYPE)
267 * - media (UPLOAD_MEDIA_TYPE)
268 * - multipart (UPLOAD_MULTIPART_TYPE)
269 * @param string|false $meta
270 * @return string
271 * @visible for testing
272 */
273 public function getUploadType($meta)
274 {
275 if ($this->resumable) {
276 return self::UPLOAD_RESUMABLE_TYPE;
277 }
278
279 if (false == $meta && $this->data) {
280 return self::UPLOAD_MEDIA_TYPE;
281 }
282
283 return self::UPLOAD_MULTIPART_TYPE;
284 }
285
286 public function getResumeUri()
287 {
288 if (null === $this->resumeUri) {
289 $this->resumeUri = $this->fetchResumeUri();
290 }
291
292 return $this->resumeUri;
293 }
294
295 private function fetchResumeUri()
296 {
297 $body = $this->request->getBody();
298 $headers = [
299 'content-type' => 'application/json; charset=UTF-8',
300 'content-length' => $body->getSize(),
301 'x-upload-content-type' => $this->mimeType,
302 'x-upload-content-length' => $this->size,
303 'expect' => '',
304 ];
305 foreach ($headers as $key => $value) {
306 $this->request = $this->request->withHeader($key, $value);
307 }
308
309 $response = $this->client->execute($this->request, false);
310 $location = $response->getHeaderLine('location');
311 $code = $response->getStatusCode();
312
313 if (200 == $code && true == $location) {
314 return $location;
315 }
316
317 $message = $code;
318 $body = json_decode((string) $this->request->getBody(), true);
319 if (isset($body['error']['errors'])) {
320 $message .= ': ';
321 foreach ($body['error']['errors'] as $error) {
322 $message .= "{$error['domain']}, {$error['message']};";
323 }
324 $message = rtrim($message, ';');
325 }
326
327 $error = "Failed to start the resumable upload (HTTP {$message})";
328 $this->client->getLogger()->error($error);
329
330 throw new GoogleException($error);
331 }
332
333 private function transformToUploadUrl()
334 {
335 $parts = parse_url((string) $this->request->getUri());
336 if (!isset($parts['path'])) {
337 $parts['path'] = '';
338 }
339 $parts['path'] = '/upload' . $parts['path'];
340 $uri = Uri::fromParts($parts);
341 $this->request = $this->request->withUri($uri);
342 }
343
344 public function setChunkSize($chunkSize)
345 {
346 $this->chunkSize = $chunkSize;
347 }
348
349 public function getRequest()
350 {
351 return $this->request;
352 }
353}
Note: See TracBrowser for help on using the repository browser.