Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
}