Overview

Namespaces

  • OpenCloud
    • Autoscale
      • Resource
    • CloudMonitoring
      • Exception
      • Resource
    • Common
      • Collection
      • Constants
      • Exceptions
      • Http
        • Message
      • Log
      • Resource
      • Service
    • Compute
      • Constants
      • Exception
      • Resource
    • Database
      • Resource
    • DNS
      • Collection
      • Resource
    • Identity
      • Constants
      • Resource
    • Image
      • Enum
      • Resource
        • JsonPatch
        • Schema
    • LoadBalancer
      • Enum
      • Resource
    • ObjectStore
      • Constants
      • Exception
      • Resource
      • Upload
    • Orchestration
    • Queues
      • Exception
      • Resource
    • Volume
      • Resource
  • PHP

Classes

  • AbstractContainer
  • AbstractResource
  • Account
  • CDNContainer
  • Container
  • ContainerMetadata
  • DataObject
  • Overview
  • Namespace
  • Class
  • Tree
  • Download
  1: <?php
  2: /**
  3:  * Copyright 2012-2014 Rackspace US, 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: namespace OpenCloud\ObjectStore\Resource;
 19: 
 20: use Guzzle\Http\EntityBody;
 21: use Guzzle\Http\Exception\BadResponseException;
 22: use Guzzle\Http\Exception\ClientErrorResponseException;
 23: use Guzzle\Http\Message\Response;
 24: use Guzzle\Http\Url;
 25: use OpenCloud\Common\Constants\Size;
 26: use OpenCloud\Common\Exceptions;
 27: use OpenCloud\Common\Service\ServiceInterface;
 28: use OpenCloud\ObjectStore\Constants\Header as HeaderConst;
 29: use OpenCloud\ObjectStore\Exception\ContainerException;
 30: use OpenCloud\ObjectStore\Exception\ObjectNotFoundException;
 31: use OpenCloud\ObjectStore\Upload\DirectorySync;
 32: use OpenCloud\ObjectStore\Upload\TransferBuilder;
 33: 
 34: /**
 35:  * A container is a storage compartment for your data and provides a way for you
 36:  * to organize your data. You can think of a container as a folder in Windows
 37:  * or a directory in Unix. The primary difference between a container and these
 38:  * other file system concepts is that containers cannot be nested.
 39:  *
 40:  * A container can also be CDN-enabled (for public access), in which case you
 41:  * will need to interact with a CDNContainer object instead of this one.
 42:  */
 43: class Container extends AbstractContainer
 44: {
 45:     const METADATA_LABEL = 'Container';
 46: 
 47:     /**
 48:      * This is the object that holds all the CDN functionality. This Container therefore acts as a simple wrapper and is
 49:      * interested in storage concerns only.
 50:      *
 51:      * @var CDNContainer|null
 52:      */
 53:     private $cdn;
 54: 
 55:     public function __construct(ServiceInterface $service, $data = null)
 56:     {
 57:         parent::__construct($service, $data);
 58: 
 59:         // Set metadata items for collection listings
 60:         if (isset($data->count)) {
 61:             $this->metadata->setProperty('Object-Count', $data->count);
 62:         }
 63:         if (isset($data->bytes)) {
 64:             $this->metadata->setProperty('Bytes-Used', $data->bytes);
 65:         }
 66:     }
 67: 
 68:     /**
 69:      * Factory method that instantiates an object from a Response object.
 70:      *
 71:      * @param Response         $response
 72:      * @param ServiceInterface $service
 73:      * @return static
 74:      */
 75:     public static function fromResponse(Response $response, ServiceInterface $service)
 76:     {
 77:         $self = parent::fromResponse($response, $service);
 78: 
 79:         $segments = Url::factory($response->getEffectiveUrl())->getPathSegments();
 80:         $self->name = end($segments);
 81: 
 82:         return $self;
 83:     }
 84: 
 85:     /**
 86:      * Get the CDN object.
 87:      *
 88:      * @return null|CDNContainer
 89:      * @throws \OpenCloud\Common\Exceptions\CdnNotAvailableError
 90:      */
 91:     public function getCdn()
 92:     {
 93:         if (!$this->isCdnEnabled()) {
 94:             throw new Exceptions\CdnNotAvailableError(
 95:                 'Either this container is not CDN-enabled or the CDN is not available'
 96:             );
 97:         }
 98: 
 99:         return $this->cdn;
100:     }
101: 
102:     /**
103:      * It would be awesome to put these convenience methods (which are identical to the ones in the Account object) in
104:      * a trait, but we have to wait for v5.3 EOL first...
105:      *
106:      * @return null|string|int
107:      */
108:     public function getObjectCount()
109:     {
110:         return $this->metadata->getProperty('Object-Count');
111:     }
112: 
113:     /**
114:      * @return null|string|int
115:      */
116:     public function getBytesUsed()
117:     {
118:         return $this->metadata->getProperty('Bytes-Used');
119:     }
120: 
121:     /**
122:      * @param $value
123:      * @return mixed
124:      */
125:     public function setCountQuota($value)
126:     {
127:         $this->metadata->setProperty('Quota-Count', $value);
128: 
129:         return $this->saveMetadata($this->metadata->toArray());
130:     }
131: 
132:     /**
133:      * @return null|string|int
134:      */
135:     public function getCountQuota()
136:     {
137:         return $this->metadata->getProperty('Quota-Count');
138:     }
139: 
140:     /**
141:      * @param $value
142:      * @return mixed
143:      */
144:     public function setBytesQuota($value)
145:     {
146:         $this->metadata->setProperty('Quota-Bytes', $value);
147: 
148:         return $this->saveMetadata($this->metadata->toArray());
149:     }
150: 
151:     /**
152:      * @return null|string|int
153:      */
154:     public function getBytesQuota()
155:     {
156:         return $this->metadata->getProperty('Quota-Bytes');
157:     }
158: 
159:     public function delete($deleteObjects = false)
160:     {
161:         if ($deleteObjects === true) {
162:             $this->deleteAllObjects();
163:         }
164: 
165:         try {
166:             return $this->getClient()->delete($this->getUrl())->send();
167:         } catch (ClientErrorResponseException $e) {
168:             if ($e->getResponse()->getStatusCode() == 409) {
169:                 throw new ContainerException(sprintf(
170:                     'The API returned this error: %s. You might have to delete all existing objects before continuing.',
171:                     (string) $e->getResponse()->getBody()
172:                 ));
173:             } else {
174:                 throw $e;
175:             }
176:         }
177:     }
178: 
179:     /**
180:      * Deletes all objects that this container currently contains. Useful when doing operations (like a delete) that
181:      * require an empty container first.
182:      *
183:      * @return mixed
184:      */
185:     public function deleteAllObjects()
186:     {
187:         $requests = array();
188: 
189:         $list = $this->objectList();
190: 
191:         foreach ($list as $object) {
192:             $requests[] = $this->getClient()->delete($object->getUrl());
193:         }
194: 
195:         return $this->getClient()->send($requests);
196:     }
197: 
198:     /**
199:      * Creates a Collection of objects in the container
200:      *
201:      * @param array $params associative array of parameter values.
202:      *                      * account/tenant - The unique identifier of the account/tenant.
203:      *                      * container- The unique identifier of the container.
204:      *                      * limit (Optional) - The number limit of results.
205:      *                      * marker (Optional) - Value of the marker, that the object names
206:      *                      greater in value than are returned.
207:      *                      * end_marker (Optional) - Value of the marker, that the object names
208:      *                      less in value than are returned.
209:      *                      * prefix (Optional) - Value of the prefix, which the returned object
210:      *                      names begin with.
211:      *                      * format (Optional) - Value of the serialized response format, either
212:      *                      json or xml.
213:      *                      * delimiter (Optional) - Value of the delimiter, that all the object
214:      *                      names nested in the container are returned.
215:      * @link   http://api.openstack.org for a list of possible parameter
216:      *                      names and values
217:      * @return 'OpenCloud\Common\Collection
218:      * @throws ObjFetchError
219:      */
220:     public function objectList(array $params = array())
221:     {
222:         $params['format'] = 'json';
223: 
224:         return $this->getService()->resourceList('DataObject', $this->getUrl(null, $params), $this);
225:     }
226: 
227:     /**
228:      * Turn on access logs, which track all the web traffic that your data objects accrue.
229:      *
230:      * @return \Guzzle\Http\Message\Response
231:      */
232:     public function enableLogging()
233:     {
234:         return $this->saveMetadata($this->appendToMetadata(array(
235:             HeaderConst::ACCESS_LOGS => 'True'
236:         )));
237:     }
238: 
239:     /**
240:      * Disable access logs.
241:      *
242:      * @return \Guzzle\Http\Message\Response
243:      */
244:     public function disableLogging()
245:     {
246:         return $this->saveMetadata($this->appendToMetadata(array(
247:             HeaderConst::ACCESS_LOGS => 'False'
248:         )));
249:     }
250: 
251:     /**
252:      * Enable this container for public CDN access.
253:      *
254:      * @param null $ttl
255:      */
256:     public function enableCdn($ttl = null)
257:     {
258:         $headers = array('X-CDN-Enabled' => 'True');
259:         if ($ttl) {
260:             $headers['X-TTL'] = (int) $ttl;
261:         }
262: 
263:         $this->getClient()->put($this->getCdnService()->getUrl($this->name), $headers)->send();
264:         $this->refresh();
265:     }
266: 
267:     /**
268:      * Disables the containers CDN function. Note that the container will still
269:      * be available on the CDN until its TTL expires.
270:      *
271:      * @return \Guzzle\Http\Message\Response
272:      */
273:     public function disableCdn()
274:     {
275:         $headers = array('X-CDN-Enabled' => 'False');
276: 
277:         return $this->getClient()
278:             ->put($this->getCdnService()->getUrl($this->name), $headers)
279:             ->send();
280:     }
281: 
282:     public function refresh($id = null, $url = null)
283:     {
284:         $headers = $this->createRefreshRequest()->send()->getHeaders();
285:         $this->setMetadata($headers, true);
286: 
287:         try {
288:             if (null !== ($cdnService = $this->getService()->getCDNService())) {
289:                 $cdn = new CDNContainer($cdnService);
290:                 $cdn->setName($this->name);
291: 
292:                 $response = $cdn->createRefreshRequest()->send();
293: 
294:                 if ($response->isSuccessful()) {
295:                     $this->cdn = $cdn;
296:                     $this->cdn->setMetadata($response->getHeaders(), true);
297:                 }
298:             } else {
299:                 $this->cdn = null;
300:             }
301:         } catch (ClientErrorResponseException $e) {
302:         }
303:     }
304: 
305:     /**
306:      * Get either a fresh data object (no $info), or get an existing one by passing in data for population.
307:      *
308:      * @param  mixed $info
309:      * @return DataObject
310:      */
311:     public function dataObject($info = null)
312:     {
313:         return new DataObject($this, $info);
314:     }
315: 
316:     /**
317:      * Retrieve an object from the API. Apart from using the name as an
318:      * identifier, you can also specify additional headers that will be used
319:      * fpr a conditional GET request. These are
320:      *
321:      * * `If-Match'
322:      * * `If-None-Match'
323:      * * `If-Modified-Since'
324:      * * `If-Unmodified-Since'
325:      * * `Range'  For example:
326:      *      bytes=-5    would mean the last 5 bytes of the object
327:      *      bytes=10-15 would mean 5 bytes after a 10 byte offset
328:      *      bytes=32-   would mean all dat after first 32 bytes
329:      *
330:      * These are also documented in RFC 2616.
331:      *
332:      * @param string $name
333:      * @param array  $headers
334:      * @return DataObject
335:      */
336:     public function getObject($name, array $headers = array())
337:     {
338:         try {
339:             $response = $this->getClient()
340:                 ->get($this->getUrl($name), $headers)
341:                 ->send();
342:         } catch (BadResponseException $e) {
343:             if ($e->getResponse()->getStatusCode() == 404) {
344:                 throw ObjectNotFoundException::factory($name, $e);
345:             }
346:             throw $e;
347:         }
348: 
349:         return $this->dataObject()
350:             ->populateFromResponse($response)
351:             ->setName($name);
352:     }
353: 
354:     /**
355:      * Essentially the same as {@see getObject()}, except only the metadata is fetched from the API.
356:      * This is useful for cases when the user does not want to fetch the full entity body of the
357:      * object, only its metadata.
358:      *
359:      * @param       $name
360:      * @param array $headers
361:      * @return $this
362:      */
363:     public function getPartialObject($name, array $headers = array())
364:     {
365:         $response = $this->getClient()
366:             ->head($this->getUrl($name), $headers)
367:             ->send();
368: 
369:         return $this->dataObject()
370:             ->populateFromResponse($response)
371:             ->setName($name);
372:     }
373: 
374:     /**
375:      * Check if an object exists inside a container. Uses {@see getPartialObject()}
376:      * to save on bandwidth and time.
377:      *
378:      * @param  $name    Object name
379:      * @return boolean  True, if object exists in this container; false otherwise.
380:      */
381:     public function objectExists($name)
382:     {
383:         try {
384:             // Send HEAD request to check resource existence
385:             $url = clone $this->getUrl();
386:             $url->addPath((string) $name);
387:             $this->getClient()->head($url)->send();
388:         } catch (ClientErrorResponseException $e) {
389:             // If a 404 was returned, then the object doesn't exist
390:             if ($e->getResponse()->getStatusCode() === 404) {
391:                 return false;
392:             } else {
393:                 throw $e;
394:             }
395:         }
396: 
397:         return true;
398:     }
399: 
400:     /**
401:      * Upload a single file to the API.
402:      *
403:      * @param       $name    Name that the file will be saved as in your container.
404:      * @param       $data    Either a string or stream representation of the file contents to be uploaded.
405:      * @param array $headers Optional headers that will be sent with the request (useful for object metadata).
406:      * @return DataObject
407:      */
408:     public function uploadObject($name, $data, array $headers = array())
409:     {
410:         $entityBody = EntityBody::factory($data);
411: 
412:         $url = clone $this->getUrl();
413:         $url->addPath($name);
414: 
415:         // @todo for new major release: Return response rather than populated DataObject
416: 
417:         $response = $this->getClient()->put($url, $headers, $entityBody)->send();
418: 
419:         return $this->dataObject()
420:             ->populateFromResponse($response)
421:             ->setName($name)
422:             ->setContent($entityBody);
423:     }
424: 
425:     /**
426:      * Upload an array of objects for upload. This method optimizes the upload procedure by batching requests for
427:      * faster execution. This is a very useful procedure when you just have a bunch of unremarkable files to be
428:      * uploaded quickly. Each file must be under 5GB.
429:      *
430:      * @param array $files   With the following array structure:
431:      *                       `name' Name that the file will be saved as in your container. Required.
432:      *                       `path' Path to an existing file, OR
433:      *                       `body' Either a string or stream representation of the file contents to be uploaded.
434:      * @param array $headers Optional headers that will be sent with the request (useful for object metadata).
435:      *
436:      * @throws \OpenCloud\Common\Exceptions\InvalidArgumentError
437:      * @return \Guzzle\Http\Message\Response
438:      */
439:     public function uploadObjects(array $files, array $commonHeaders = array())
440:     {
441:         $requests = $entities = array();
442: 
443:         foreach ($files as $entity) {
444: 
445:             if (empty($entity['name'])) {
446:                 throw new Exceptions\InvalidArgumentError('You must provide a name.');
447:             }
448: 
449:             if (!empty($entity['path']) && file_exists($entity['path'])) {
450:                 $body = fopen($entity['path'], 'r+');
451:             } elseif (!empty($entity['body'])) {
452:                 $body = $entity['body'];
453:             } else {
454:                 throw new Exceptions\InvalidArgumentError('You must provide either a readable path or a body');
455:             }
456: 
457:             $entityBody = $entities[] = EntityBody::factory($body);
458: 
459:             // @codeCoverageIgnoreStart
460:             if ($entityBody->getContentLength() >= 5 * Size::GB) {
461:                 throw new Exceptions\InvalidArgumentError(
462:                     'For multiple uploads, you cannot upload more than 5GB per '
463:                     . ' file. Use the UploadBuilder for larger files.'
464:                 );
465:             }
466:             // @codeCoverageIgnoreEnd
467: 
468:             // Allow custom headers and common
469:             $headers = (isset($entity['headers'])) ? $entity['headers'] : $commonHeaders;
470: 
471:             $url = clone $this->getUrl();
472:             $url->addPath($entity['name']);
473: 
474:             $requests[] = $this->getClient()->put($url, $headers, $entityBody);
475:         }
476: 
477:         $responses = $this->getClient()->send($requests);
478: 
479:         foreach ($entities as $entity) {
480:             $entity->close();
481:         }
482: 
483:         return $responses;
484:     }
485: 
486:     /**
487:      * When uploading large files (+5GB), you need to upload the file as chunks using multibyte transfer. This method
488:      * sets up the transfer, and in order to execute the transfer, you need to call upload() on the returned object.
489:      *
490:      * @param array Options
491:      * @see \OpenCloud\ObjectStore\Upload\UploadBuilder::setOptions for a list of accepted options.
492:      * @throws \OpenCloud\Common\Exceptions\InvalidArgumentError
493:      * @return mixed
494:      */
495:     public function setupObjectTransfer(array $options = array())
496:     {
497:         // Name is required
498:         if (empty($options['name'])) {
499:             throw new Exceptions\InvalidArgumentError('You must provide a name.');
500:         }
501: 
502:         // As is some form of entity body
503:         if (!empty($options['path']) && file_exists($options['path'])) {
504:             $body = fopen($options['path'], 'r+');
505:         } elseif (!empty($options['body'])) {
506:             $body = $options['body'];
507:         } else {
508:             throw new Exceptions\InvalidArgumentError('You must provide either a readable path or a body');
509:         }
510: 
511:         // Build upload
512:         $transfer = TransferBuilder::newInstance()
513:             ->setOption('objectName', $options['name'])
514:             ->setEntityBody(EntityBody::factory($body))
515:             ->setContainer($this);
516: 
517:         // Add extra options
518:         if (!empty($options['metadata'])) {
519:             $transfer->setOption('metadata', $options['metadata']);
520:         }
521:         if (!empty($options['partSize'])) {
522:             $transfer->setOption('partSize', $options['partSize']);
523:         }
524:         if (!empty($options['concurrency'])) {
525:             $transfer->setOption('concurrency', $options['concurrency']);
526:         }
527:         if (!empty($options['progress'])) {
528:             $transfer->setOption('progress', $options['progress']);
529:         }
530: 
531:         return $transfer->build();
532:     }
533: 
534:     /**
535:      * Upload the contents of a local directory to a remote container, effectively syncing them.
536:      *
537:      * @param $path The local path to the directory.
538:      */
539:     public function uploadDirectory($path)
540:     {
541:         $sync = DirectorySync::factory($path, $this);
542:         $sync->execute();
543:     }
544: 
545:     public function isCdnEnabled()
546:     {
547:         return ($this->cdn instanceof CDNContainer) && $this->cdn->isCdnEnabled();
548:     }
549: }
550: 
PHP OpenCloud API API documentation generated by ApiGen 2.8.0