<?php 
/** 
 * Copyright 2012 Google Inc. 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */ 
 
/** 
 * @author Chirag Shah <[email protected]> 
 * 
 */ 
class Google_MediaFileUpload { 
  const UPLOAD_MEDIA_TYPE = 'media'; 
  const UPLOAD_MULTIPART_TYPE = 'multipart'; 
  const UPLOAD_RESUMABLE_TYPE = 'resumable'; 
 
  /** @var string $mimeType */ 
  public $mimeType; 
 
  /** @var string $data */ 
  public $data; 
 
  /** @var bool $resumable */ 
  public $resumable; 
 
  /** @var int $chunkSize */ 
  public $chunkSize; 
 
  /** @var int $size */ 
  public $size; 
 
  /** @var string $resumeUri */ 
  public $resumeUri; 
 
  /** @var int $progress */ 
  public $progress; 
 
  /** 
   * @param $mimeType string 
   * @param $data string The bytes you want to upload. 
   * @param $resumable bool 
   * @param bool $chunkSize File will be uploaded in chunks of this many bytes. 
   * only used if resumable=True 
   */ 
  public function __construct($mimeType, $data, $resumable=false, $chunkSize=false) { 
    $this->mimeType = $mimeType; 
    $this->data = $data; 
    $this->size = strlen($this->data); 
    $this->resumable = $resumable; 
    if(!$chunkSize) { 
      $chunkSize = 256 * 1024; 
    } 
    $this->chunkSize = $chunkSize; 
    $this->progress = 0; 
  } 
 
  public function setFileSize($size) { 
    $this->size = $size; 
  } 
 
  /** 
   * @static 
   * @param $meta 
   * @param $params 
   * @return array|bool 
   */ 
  public static function process($meta, &$params) { 
    $payload = array(); 
    $meta = is_string($meta) ? json_decode($meta, true) : $meta; 
    $uploadType = self::getUploadType($meta, $payload, $params); 
    if (!$uploadType) { 
      // Process as a normal API request. 
      return false; 
    } 
 
    // Process as a media upload request. 
    $params['uploadType'] = array( 
        'type' => 'string', 
        'location' => 'query', 
        'value' => $uploadType, 
    ); 
 
    $mimeType = isset($params['mimeType']) 
        ? $params['mimeType']['value'] 
        : false; 
    unset($params['mimeType']); 
 
    if (!$mimeType) { 
      $mimeType = $payload['content-type']; 
    } 
 
    if (isset($params['file'])) { 
      // This is a standard file upload with curl. 
      $file = $params['file']['value']; 
      unset($params['file']); 
      return self::processFileUpload($file, $mimeType); 
    } 
 
    $data = isset($params['data']) 
        ? $params['data']['value'] 
        : false; 
    unset($params['data']); 
 
    if (self::UPLOAD_RESUMABLE_TYPE == $uploadType) { 
      $payload['content-type'] = $mimeType; 
      $payload['postBody'] = is_string($meta) ? $meta : json_encode($meta); 
 
    } elseif (self::UPLOAD_MEDIA_TYPE == $uploadType) { 
      // This is a simple media upload. 
      $payload['content-type'] = $mimeType; 
      $payload['postBody'] = $data; 
    } 
 
    elseif (self::UPLOAD_MULTIPART_TYPE == $uploadType) { 
      // This is a multipart/related upload. 
      $boundary = isset($params['boundary']['value']) ? $params['boundary']['value'] : mt_rand(); 
      $boundary = str_replace('"', '', $boundary); 
      $payload['content-type'] = 'multipart/related; boundary=' . $boundary; 
      $related = "--$boundary\r\n"; 
      $related .= "Content-Type: application/json; charset=UTF-8\r\n"; 
      $related .= "\r\n" . json_encode($meta) . "\r\n"; 
      $related .= "--$boundary\r\n"; 
      $related .= "Content-Type: $mimeType\r\n"; 
      $related .= "Content-Transfer-Encoding: base64\r\n"; 
      $related .= "\r\n" . base64_encode($data) . "\r\n"; 
      $related .= "--$boundary--"; 
      $payload['postBody'] = $related; 
    } 
 
    return $payload; 
  } 
 
  /** 
   * Prepares a standard file upload via cURL. 
   * @param $file 
   * @param $mime 
   * @return array Includes the processed file name. 
   * @visible For testing. 
   */ 
  public static function processFileUpload($file, $mime) { 
    if (!$file) return array(); 
    if (substr($file, 0, 1) != '@') { 
      $file = '@' . $file; 
    } 
 
    // This is a standard file upload with curl. 
    $params = array('postBody' => array('file' => $file)); 
    if ($mime) { 
      $params['content-type'] = $mime; 
    } 
 
    return $params; 
  } 
 
  /** 
   * Valid upload types: 
   * - resumable (UPLOAD_RESUMABLE_TYPE) 
   * - media (UPLOAD_MEDIA_TYPE) 
   * - multipart (UPLOAD_MULTIPART_TYPE) 
   * - none (false) 
   * @param $meta 
   * @param $payload 
   * @param $params 
   * @return bool|string 
   */ 
  public static function getUploadType($meta, &$payload, &$params) { 
    if (isset($params['mediaUpload']) 
        && get_class($params['mediaUpload']['value']) == 'Google_MediaFileUpload') { 
      $upload = $params['mediaUpload']['value']; 
      unset($params['mediaUpload']); 
      $payload['content-type'] = $upload->mimeType; 
      if (isset($upload->resumable) && $upload->resumable) { 
        return self::UPLOAD_RESUMABLE_TYPE; 
      } 
    } 
 
    // Allow the developer to override the upload type. 
    if (isset($params['uploadType'])) { 
      return $params['uploadType']['value']; 
    } 
 
    $data = isset($params['data']['value']) 
        ? $params['data']['value'] : false; 
 
    if (false == $data && false == isset($params['file'])) { 
      // No upload data available. 
      return false; 
    } 
 
    if (isset($params['file'])) { 
      return self::UPLOAD_MEDIA_TYPE; 
    } 
 
    if (false == $meta) { 
      return self::UPLOAD_MEDIA_TYPE; 
    } 
 
    return self::UPLOAD_MULTIPART_TYPE; 
  } 
 
 
  public function nextChunk(Google_HttpRequest $req, $chunk=false) { 
    if (false == $this->resumeUri) { 
      $this->resumeUri = $this->getResumeUri($req); 
    } 
 
    if (false == $chunk) { 
      $chunk = substr($this->data, $this->progress, $this->chunkSize); 
    } 
 
    $lastBytePos = $this->progress + strlen($chunk) - 1; 
    $headers = array( 
      'content-range' => "bytes $this->progress-$lastBytePos/$this->size", 
      'content-type' => $req->getRequestHeader('content-type'), 
      'content-length' => $this->chunkSize, 
      'expect' => '', 
    ); 
 
    $httpRequest = new Google_HttpRequest($this->resumeUri, 'PUT', $headers, $chunk); 
    $response = Google_Client::$io->authenticatedRequest($httpRequest); 
    $code = $response->getResponseHttpCode(); 
    if (308 == $code) { 
      $range = explode('-', $response->getResponseHeader('range')); 
      $this->progress = $range[1] + 1; 
      return false; 
    } else { 
      return Google_REST::decodeHttpResponse($response); 
    } 
  } 
 
  private function getResumeUri(Google_HttpRequest $httpRequest) { 
    $result = null; 
    $body = $httpRequest->getPostBody(); 
    if ($body) { 
      $httpRequest->setRequestHeaders(array( 
        'content-type' => 'application/json; charset=UTF-8', 
        'content-length' => Google_Utils::getStrLen($body), 
        'x-upload-content-type' => $this->mimeType, 
        'x-upload-content-length' => $this->size, 
        'expect' => '', 
      )); 
    } 
 
    $response = Google_Client::$io->makeRequest($httpRequest); 
    $location = $response->getResponseHeader('location'); 
    $code = $response->getResponseHttpCode(); 
    if (200 == $code && true == $location) { 
      return $location; 
    } 
    throw new Google_Exception("Failed to start the resumable upload"); 
  } 
}
 
 |