Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]
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 18 if (!class_exists('Google_Client')) { 19 require_once dirname(__FILE__) . '/../autoload.php'; 20 } 21 22 /** 23 * Manage large file uploads, which may be media but can be any type 24 * of sizable data. 25 */ 26 #[AllowDynamicProperties] 27 class Google_Http_MediaFileUpload 28 { 29 const UPLOAD_MEDIA_TYPE = 'media'; 30 const UPLOAD_MULTIPART_TYPE = 'multipart'; 31 const UPLOAD_RESUMABLE_TYPE = 'resumable'; 32 33 /** @var string $mimeType */ 34 private $mimeType; 35 36 /** @var string $data */ 37 private $data; 38 39 /** @var bool $resumable */ 40 private $resumable; 41 42 /** @var int $chunkSize */ 43 private $chunkSize; 44 45 /** @var int $size */ 46 private $size; 47 48 /** @var string $resumeUri */ 49 private $resumeUri; 50 51 /** @var int $progress */ 52 private $progress; 53 54 /** @var Google_Client */ 55 private $client; 56 57 /** @var Google_Http_Request */ 58 private $request; 59 60 /** @var string */ 61 private $boundary; 62 63 /** 64 * Result code from last HTTP call 65 * @var int 66 */ 67 private $httpResultCode; 68 69 /** 70 * @param $mimeType string 71 * @param $data string The bytes you want to upload. 72 * @param $resumable bool 73 * @param bool $chunkSize File will be uploaded in chunks of this many bytes. 74 * only used if resumable=True 75 */ 76 public function __construct( 77 Google_Client $client, 78 Google_Http_Request $request, 79 $mimeType, 80 $data, 81 $resumable = false, 82 $chunkSize = false, 83 $boundary = false 84 ) { 85 $this->client = $client; 86 $this->request = $request; 87 $this->mimeType = $mimeType; 88 $this->data = $data; 89 $this->size = strlen($this->data); 90 $this->resumable = $resumable; 91 if (!$chunkSize) { 92 $chunkSize = 256 * 1024; 93 } 94 $this->chunkSize = $chunkSize; 95 $this->progress = 0; 96 $this->boundary = $boundary; 97 98 // Process Media Request 99 $this->process(); 100 } 101 102 /** 103 * Set the size of the file that is being uploaded. 104 * @param $size - int file size in bytes 105 */ 106 public function setFileSize($size) 107 { 108 $this->size = $size; 109 } 110 111 /** 112 * Return the progress on the upload 113 * @return int progress in bytes uploaded. 114 */ 115 public function getProgress() 116 { 117 return $this->progress; 118 } 119 120 /** 121 * Return the HTTP result code from the last call made. 122 * @return int code 123 */ 124 public function getHttpResultCode() 125 { 126 return $this->httpResultCode; 127 } 128 129 /** 130 * Sends a PUT-Request to google drive and parses the response, 131 * setting the appropiate variables from the response() 132 * 133 * @param Google_Http_Request $httpRequest the Reuqest which will be send 134 * 135 * @return false|mixed false when the upload is unfinished or the decoded http response 136 * 137 */ 138 private function makePutRequest(Google_Http_Request $httpRequest) 139 { 140 if ($this->client->getClassConfig("Google_Http_Request", "enable_gzip_for_uploads")) { 141 $httpRequest->enableGzip(); 142 } else { 143 $httpRequest->disableGzip(); 144 } 145 146 $response = $this->client->getIo()->makeRequest($httpRequest); 147 $response->setExpectedClass($this->request->getExpectedClass()); 148 $code = $response->getResponseHttpCode(); 149 $this->httpResultCode = $code; 150 151 if (308 == $code) { 152 // Track the amount uploaded. 153 $range = explode('-', $response->getResponseHeader('range')); 154 $this->progress = $range[1] + 1; 155 156 // Allow for changing upload URLs. 157 $location = $response->getResponseHeader('location'); 158 if ($location) { 159 $this->resumeUri = $location; 160 } 161 162 // No problems, but upload not complete. 163 return false; 164 } else { 165 return Google_Http_REST::decodeHttpResponse($response, $this->client); 166 } 167 } 168 169 /** 170 * Send the next part of the file to upload. 171 * @param [$chunk] the next set of bytes to send. If false will used $data passed 172 * at construct time. 173 */ 174 public function nextChunk($chunk = false) 175 { 176 if (false == $this->resumeUri) { 177 $this->resumeUri = $this->fetchResumeUri(); 178 } 179 180 if (false == $chunk) { 181 $chunk = substr($this->data, $this->progress, $this->chunkSize); 182 } 183 $lastBytePos = $this->progress + strlen($chunk) - 1; 184 $headers = array( 185 'content-range' => "bytes $this->progress-$lastBytePos/$this->size", 186 'content-type' => $this->request->getRequestHeader('content-type'), 187 'content-length' => $this->chunkSize, 188 'expect' => '', 189 ); 190 191 $httpRequest = new Google_Http_Request( 192 $this->resumeUri, 193 'PUT', 194 $headers, 195 $chunk 196 ); 197 return $this->makePutRequest($httpRequest); 198 } 199 200 /** 201 * Resume a previously unfinished upload 202 * @param $resumeUri the resume-URI of the unfinished, resumable upload. 203 */ 204 public function resume($resumeUri) 205 { 206 $this->resumeUri = $resumeUri; 207 $headers = array( 208 'content-range' => "bytes */$this->size", 209 'content-length' => 0, 210 ); 211 $httpRequest = new Google_Http_Request( 212 $this->resumeUri, 213 'PUT', 214 $headers 215 ); 216 return $this->makePutRequest($httpRequest); 217 } 218 219 /** 220 * @return array|bool 221 * @visible for testing 222 */ 223 private function process() 224 { 225 $postBody = false; 226 $contentType = false; 227 228 $meta = $this->request->getPostBody(); 229 $meta = is_string($meta) ? json_decode($meta, true) : $meta; 230 231 $uploadType = $this->getUploadType($meta); 232 $this->request->setQueryParam('uploadType', $uploadType); 233 $this->transformToUploadUrl(); 234 $mimeType = $this->mimeType ? 235 $this->mimeType : 236 $this->request->getRequestHeader('content-type'); 237 238 if (self::UPLOAD_RESUMABLE_TYPE == $uploadType) { 239 $contentType = $mimeType; 240 $postBody = is_string($meta) ? $meta : json_encode($meta); 241 } else if (self::UPLOAD_MEDIA_TYPE == $uploadType) { 242 $contentType = $mimeType; 243 $postBody = $this->data; 244 } else if (self::UPLOAD_MULTIPART_TYPE == $uploadType) { 245 // This is a multipart/related upload. 246 $boundary = $this->boundary ? $this->boundary : mt_rand(); 247 $boundary = str_replace('"', '', $boundary); 248 $contentType = 'multipart/related; boundary=' . $boundary; 249 $related = "--$boundary\r\n"; 250 $related .= "Content-Type: application/json; charset=UTF-8\r\n"; 251 $related .= "\r\n" . json_encode($meta) . "\r\n"; 252 $related .= "--$boundary\r\n"; 253 $related .= "Content-Type: $mimeType\r\n"; 254 $related .= "Content-Transfer-Encoding: base64\r\n"; 255 $related .= "\r\n" . base64_encode($this->data) . "\r\n"; 256 $related .= "--$boundary--"; 257 $postBody = $related; 258 } 259 260 $this->request->setPostBody($postBody); 261 262 if (isset($contentType) && $contentType) { 263 $contentTypeHeader['content-type'] = $contentType; 264 $this->request->setRequestHeaders($contentTypeHeader); 265 } 266 } 267 268 private function transformToUploadUrl() 269 { 270 $base = $this->request->getBaseComponent(); 271 $this->request->setBaseComponent($base . '/upload'); 272 } 273 274 /** 275 * Valid upload types: 276 * - resumable (UPLOAD_RESUMABLE_TYPE) 277 * - media (UPLOAD_MEDIA_TYPE) 278 * - multipart (UPLOAD_MULTIPART_TYPE) 279 * @param $meta 280 * @return string 281 * @visible for testing 282 */ 283 public function getUploadType($meta) 284 { 285 if ($this->resumable) { 286 return self::UPLOAD_RESUMABLE_TYPE; 287 } 288 289 if (false == $meta && $this->data) { 290 return self::UPLOAD_MEDIA_TYPE; 291 } 292 293 return self::UPLOAD_MULTIPART_TYPE; 294 } 295 296 public function getResumeUri() 297 { 298 return ( $this->resumeUri !== null ? $this->resumeUri : $this->fetchResumeUri() ); 299 } 300 301 private function fetchResumeUri() 302 { 303 $result = null; 304 $body = $this->request->getPostBody(); 305 if ($body) { 306 $headers = array( 307 'content-type' => 'application/json; charset=UTF-8', 308 'content-length' => Google_Utils::getStrLen($body), 309 'x-upload-content-type' => $this->mimeType, 310 'x-upload-content-length' => $this->size, 311 'expect' => '', 312 ); 313 $this->request->setRequestHeaders($headers); 314 } 315 316 $response = $this->client->getIo()->makeRequest($this->request); 317 $location = $response->getResponseHeader('location'); 318 $code = $response->getResponseHttpCode(); 319 320 if (200 == $code && true == $location) { 321 return $location; 322 } 323 $message = $code; 324 $body = @json_decode($response->getResponseBody()); 325 if (!empty($body->error->errors) ) { 326 $message .= ': '; 327 foreach ($body->error->errors as $error) { 328 $message .= "{$error->domain}, {$error->message};"; 329 } 330 $message = rtrim($message, ';'); 331 } 332 333 $error = "Failed to start the resumable upload (HTTP {$message})"; 334 $this->client->getLogger()->error($error); 335 throw new Google_Exception($error); 336 } 337 338 public function setChunkSize($chunkSize) 339 { 340 $this->chunkSize = $chunkSize; 341 } 342 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body