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