Overview

Namespaces

  • OpenCloud
    • Autoscale
      • Resource
    • CDN
      • Resource
    • CloudMonitoring
      • Collection
      • 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
      • Collection
      • Enum
      • Resource
    • Networking
      • Resource
    • ObjectStore
      • Constants
      • Enum
      • Exception
      • Resource
      • Upload
    • Orchestration
      • Resource
    • Queues
      • Collection
      • Exception
      • Resource
    • Volume
      • Resource
  • PHP

Classes

  • AbstractContainer
  • AbstractResource
  • Account
  • CDNContainer
  • Container
  • ContainerMetadata
  • DataObject
  • Overview
  • Namespace
  • Class
  • Tree
  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\Message\Response;
 22: use Guzzle\Http\Url;
 23: use OpenCloud\Common\Constants\Header as HeaderConst;
 24: use OpenCloud\Common\Exceptions;
 25: use OpenCloud\Common\Lang;
 26: use OpenCloud\ObjectStore\Constants\UrlType;
 27: use OpenCloud\ObjectStore\Exception\ObjectNotEmptyException;
 28: 
 29: /**
 30:  * Objects are the basic storage entities in Cloud Files. They represent the
 31:  * files and their optional metadata you upload to the system. When you upload
 32:  * objects to Cloud Files, the data is stored as-is (without compression or
 33:  * encryption) and consists of a location (container), the object's name, and
 34:  * any metadata you assign consisting of key/value pairs.
 35:  */
 36: class DataObject extends AbstractResource
 37: {
 38:     const METADATA_LABEL = 'Object';
 39: 
 40:     /**
 41:      * @var Container
 42:      */
 43:     private $container;
 44: 
 45:     /**
 46:      * @var The file name of the object
 47:      */
 48:     protected $name;
 49: 
 50:     /**
 51:      * @var EntityBody
 52:      */
 53:     protected $content;
 54: 
 55:     /**
 56:      * @var bool Whether or not this object is a "pseudo-directory"
 57:      * @link http://docs.openstack.org/trunk/openstack-object-storage/developer/content/pseudo-hierarchical-folders-directories.html
 58:      */
 59:     protected $directory = false;
 60: 
 61:     /**
 62:      * @var string The object's content type
 63:      */
 64:     protected $contentType;
 65: 
 66:     /**
 67:      * @var The size of this object.
 68:      */
 69:     protected $contentLength;
 70: 
 71:     /**
 72:      * @var string Date of last modification.
 73:      */
 74:     protected $lastModified;
 75: 
 76:     /**
 77:      * @var string Etag.
 78:      */
 79:     protected $etag;
 80:     
 81:     /**
 82:      * @var string Manifest. Can be null so we use false to mean unset.
 83:      */
 84:     protected $manifest = false;
 85: 
 86:     /**
 87:      * Also need to set Container parent and handle pseudo-directories.
 88:      * {@inheritDoc}
 89:      *
 90:      * @param Container $container
 91:      * @param null      $data
 92:      */
 93:     public function __construct(Container $container, $data = null)
 94:     {
 95:         $this->setContainer($container);
 96: 
 97:         parent::__construct($container->getService());
 98: 
 99:         // For pseudo-directories, we need to ensure the name is set
100:         if (!empty($data->subdir)) {
101:             $this->setName($data->subdir)->setDirectory(true);
102: 
103:             return;
104:         }
105: 
106:         $this->populate($data);
107:     }
108: 
109:     /**
110:      * A collection list of DataObjects contains a different data structure than the one returned for the
111:      * "Retrieve Object" operation. So we need to stock the values differently.
112:      * {@inheritDoc}
113:      */
114:     public function populate($info, $setObjects = true)
115:     {
116:         parent::populate($info, $setObjects);
117: 
118:         if (isset($info->bytes)) {
119:             $this->setContentLength($info->bytes);
120:         }
121:         if (isset($info->last_modified)) {
122:             $this->setLastModified($info->last_modified);
123:         }
124:         if (isset($info->content_type)) {
125:             $this->setContentType($info->content_type);
126:         }
127:         if (isset($info->hash)) {
128:             $this->setEtag($info->hash);
129:         }
130:     }
131: 
132:     /**
133:      * Takes a response and stocks common values from both the body and the headers.
134:      *
135:      * @param Response $response
136:      * @return $this
137:      */
138:     public function populateFromResponse(Response $response)
139:     {
140:         $this->content = $response->getBody();
141: 
142:         $headers = $response->getHeaders();
143: 
144:         return $this->setMetadata($headers, true)
145:             ->setContentType((string) $headers[HeaderConst::CONTENT_TYPE])
146:             ->setLastModified((string) $headers[HeaderConst::LAST_MODIFIED])
147:             ->setContentLength((string) $headers[HeaderConst::CONTENT_LENGTH])
148:             ->setEtag((string) $headers[HeaderConst::ETAG])
149:             // do not cast to a string to allow for null (i.e. no header)
150:             ->setManifest($headers[HeaderConst::X_OBJECT_MANIFEST]);
151:     }
152: 
153:     public function refresh()
154:     {
155:         $response = $this->getService()->getClient()
156:             ->get($this->getUrl())
157:             ->send();
158: 
159:         return $this->populateFromResponse($response);
160:     }
161: 
162:     /**
163:      * @param Container $container
164:      * @return $this
165:      */
166:     public function setContainer(Container $container)
167:     {
168:         $this->container = $container;
169: 
170:         return $this;
171:     }
172: 
173:     /**
174:      * @return Container
175:      */
176:     public function getContainer()
177:     {
178:         return $this->container;
179:     }
180: 
181:     /**
182:      * @param $name string
183:      * @return $this
184:      */
185:     public function setName($name)
186:     {
187:         $this->name = $name;
188: 
189:         return $this;
190:     }
191: 
192:     /**
193:      * @return string
194:      */
195:     public function getName()
196:     {
197:         return $this->name;
198:     }
199: 
200:     /**
201:      * @param $directory bool
202:      * @return $this
203:      */
204:     public function setDirectory($directory)
205:     {
206:         $this->directory = $directory;
207: 
208:         return $this;
209:     }
210: 
211:     /**
212:      * @return bool
213:      */
214:     public function getDirectory()
215:     {
216:         return $this->directory;
217:     }
218: 
219:     /**
220:      * @return bool Is this data object a pseudo-directory?
221:      */
222:     public function isDirectory()
223:     {
224:         return (bool) $this->directory;
225:     }
226: 
227:     /**
228:      * @param  mixed $content
229:      * @return $this
230:      */
231:     public function setContent($content)
232:     {
233:         $this->etag = null;
234:         $this->contentType = null;
235:         $this->content = EntityBody::factory($content);
236: 
237:         return $this;
238:     }
239: 
240:     /**
241:      * @return EntityBody
242:      */
243:     public function getContent()
244:     {
245:         return $this->content;
246:     }
247: 
248:     /**
249:      * @param  string $contentType
250:      * @return $this
251:      */
252:     public function setContentType($contentType)
253:     {
254:         $this->contentType = $contentType;
255: 
256:         return $this;
257:     }
258: 
259:     /**
260:      * @return null|string
261:      */
262:     public function getContentType()
263:     {
264:         return $this->contentType ? : $this->content->getContentType();
265:     }
266: 
267:     /**
268:      * @param $contentType int
269:      * @return $this
270:      */
271:     public function setContentLength($contentLength)
272:     {
273:         $this->contentLength = $contentLength;
274: 
275:         return $this;
276:     }
277: 
278:     /**
279:      * @return int
280:      */
281:     public function getContentLength()
282:     {
283:         return $this->contentLength !== null ? $this->contentLength : $this->content->getContentLength();
284:     }
285: 
286:     /**
287:      * @param $etag
288:      * @return $this
289:      */
290:     public function setEtag($etag)
291:     {
292:         $this->etag = $etag;
293: 
294:         return $this;
295:     }
296: 
297:     /**
298:      * @return null|string
299:      */
300:     public function getEtag()
301:     {
302:         return $this->etag ? : $this->content->getContentMd5();
303:     }
304:     
305:     /**
306:      * @param string $manifest Path (`container/object') to set as the value to X-Object-Manifest
307:      * @return $this
308:      */
309:     protected function setManifest($manifest)
310:     {
311:         $this->manifest = $manifest;
312: 
313:         return $this;
314:     }
315: 
316:     /**
317:      * @return null|string Path (`container/object') from X-Object-Manifest header or null if the header does not exist
318:      */
319:     public function getManifest()
320:     {
321:         // only make a request if manifest has not been set (is false)
322:         return $this->manifest !== false ? $this->manifest : $this->getManifestHeader();
323:     }
324: 
325:     public function setLastModified($lastModified)
326:     {
327:         $this->lastModified = $lastModified;
328: 
329:         return $this;
330:     }
331: 
332:     public function getLastModified()
333:     {
334:         return $this->lastModified;
335:     }
336: 
337:     public function primaryKeyField()
338:     {
339:         return 'name';
340:     }
341: 
342:     public function getUrl($path = null, array $params = array())
343:     {
344:         if (!$this->name) {
345:             throw new Exceptions\NoNameError(Lang::translate('Object has no name'));
346:         }
347: 
348:         return $this->container->getUrl($this->name);
349:     }
350: 
351:     public function update($params = array())
352:     {
353:         $metadata = is_array($this->metadata) ? $this->metadata : $this->metadata->toArray();
354:         $metadata = self::stockHeaders($metadata);
355: 
356:         // merge specific properties with metadata
357:         $metadata += array(
358:             HeaderConst::CONTENT_TYPE      => $this->contentType,
359:             HeaderConst::LAST_MODIFIED     => $this->lastModified,
360:             HeaderConst::CONTENT_LENGTH    => $this->contentLength,
361:             HeaderConst::ETAG              => $this->etag,
362:             HeaderConst::X_OBJECT_MANIFEST => $this->manifest
363:         );
364: 
365:         return $this->container->uploadObject($this->name, $this->content, $metadata);
366:     }
367: 
368:     /**
369:      * @param string $destination Path (`container/object') of new object
370:      * @return \Guzzle\Http\Message\Response
371:      */
372:     public function copy($destination)
373:     {
374:         return $this->getService()
375:             ->getClient()
376:             ->createRequest('COPY', $this->getUrl(), array(
377:                 'Destination' => (string) $destination
378:             ))
379:             ->send();
380:     }
381: 
382:     public function delete($params = array())
383:     {
384:         return $this->getService()->getClient()->delete($this->getUrl())->send();
385:     }
386:     
387:     /**
388:      * Create a symlink to another named object from this object. Requires this object to be empty.
389:      *
390:      * @param string $destination Path (`container/object') of other object to symlink this object to
391:      * @return \Guzzle\Http\Message\Response The response
392:      * @throws \OpenCloud\Common\Exceptions\NoNameError if a destination name is not provided
393:      * @throws \OpenCloud\ObjectStore\Exception\ObjectNotEmptyException if $this is not an empty object
394:      */
395:     public function createSymlinkTo($destination)
396:     {
397:         if (!$this->name) {
398:             throw new Exceptions\NoNameError(Lang::translate('Object has no name'));
399:         }
400: 
401:         if ($this->getContentLength()) {
402:             throw new ObjectNotEmptyException($this->getContainer()->getName() . '/' . $this->getName());
403:         }
404: 
405:         $response = $this->getService()
406:             ->getClient()
407:             ->createRequest('PUT', $this->getUrl(), array(
408:                 HeaderConst::X_OBJECT_MANIFEST => (string) $destination
409:             ))
410:             ->send();
411: 
412:         if ($response->getStatusCode() == 201) {
413:             $this->setManifest($source);
414:         }
415: 
416:         return $response;
417:     }
418: 
419:     /**
420:      * Create a symlink to this object from another named object. Requires the other object to either not exist or be empty.
421:      *
422:      * @param string $source Path (`container/object') of other object to symlink this object from
423:      * @return DataObject The symlinked object
424:      * @throws \OpenCloud\Common\Exceptions\NoNameError if a source name is not provided
425:      * @throws \OpenCloud\ObjectStore\Exception\ObjectNotEmptyException  if object already exists and is not empty
426:      */
427:     public function createSymlinkFrom($source)
428:     {
429:         if (!strlen($source)) {
430:             throw new Exceptions\NoNameError(Lang::translate('Object has no name'));
431:         }
432: 
433:         // Use ltrim to remove leading slash from source
434:         list($containerName, $resourceName) = explode("/", ltrim($source, '/'), 2);
435:         $container = $this->getService()->getContainer($containerName);
436: 
437:         if ($container->objectExists($resourceName)) {
438:             $object = $container->getPartialObject($source);
439:             if ($object->getContentLength() > 0) {
440:                 throw new ObjectNotEmptyException($source);
441:             }
442:         }
443: 
444:         return $container->uploadObject($resourceName, 'data', array(
445:             HeaderConst::X_OBJECT_MANIFEST => (string) $this->getUrl()
446:         ));
447:     }
448: 
449:     /**
450:      * Get a temporary URL for this object.
451:      *
452:      * @link http://docs.rackspace.com/files/api/v1/cf-devguide/content/TempURL-d1a4450.html
453:      *
454:      * @param int    $expires        Expiration time in seconds
455:      * @param string $method         What method can use this URL? (`GET' or `PUT')
456:      * @param bool   $forcePublicUrl If set to TRUE, a public URL will always be used. The default is to use whatever
457:      *                               URL type the user has set for the main service.
458:      *
459:      * @return string
460:      *
461:      * @throws \OpenCloud\Common\Exceptions\InvalidArgumentError
462:      * @throws \OpenCloud\Common\Exceptions\ObjectError
463:      *
464:      */
465:     public function getTemporaryUrl($expires, $method, $forcePublicUrl = false)
466:     {
467:         $method = strtoupper($method);
468:         $expiry = time() + (int) $expires;
469: 
470:         // check for proper method
471:         if ($method != 'GET' && $method != 'PUT') {
472:             throw new Exceptions\InvalidArgumentError(sprintf(
473:                 'Bad method [%s] for TempUrl; only GET or PUT supported',
474:                 $method
475:             ));
476:         }
477: 
478:         // @codeCoverageIgnoreStart
479:         if (!($secret = $this->getService()->getAccount()->getTempUrlSecret())) {
480:             throw new Exceptions\ObjectError('Cannot produce temporary URL without an account secret.');
481:         }
482:         // @codeCoverageIgnoreEnd
483: 
484:         $url = ($forcePublicUrl === true) ? $this->getService()->getEndpoint()->getPublicUrl() : $this->getUrl();
485:         $urlPath = urldecode($url->getPath());
486:         $body = sprintf("%s\n%d\n%s", $method, $expiry, $urlPath);
487:         $hash = hash_hmac('sha1', $body, $secret);
488: 
489:         return sprintf('%s?temp_url_sig=%s&temp_url_expires=%d', $url, $hash, $expiry);
490:     }
491: 
492:     /**
493:      * Remove this object from the CDN.
494:      *
495:      * @param null $email
496:      * @return mixed
497:      */
498:     public function purge($email = null)
499:     {
500:         if (!$cdn = $this->getContainer()->getCdn()) {
501:             return false;
502:         }
503: 
504:         $url = clone $cdn->getUrl();
505:         $url->addPath($this->name);
506: 
507:         $headers = ($email !== null) ? array('X-Purge-Email' => $email) : array();
508: 
509:         return $this->getService()
510:             ->getClient()
511:             ->delete($url, $headers)
512:             ->send();
513:     }
514: 
515:     /**
516:      * @param string $type
517:      * @return bool|Url
518:      */
519:     public function getPublicUrl($type = UrlType::CDN)
520:     {
521:         $cdn = $this->container->getCdn();
522: 
523:         switch ($type) {
524:             case UrlType::CDN:
525:                 $uri = $cdn->getCdnUri();
526:                 break;
527:             case UrlType::SSL:
528:                 $uri = $cdn->getCdnSslUri();
529:                 break;
530:             case UrlType::STREAMING:
531:                 $uri = $cdn->getCdnStreamingUri();
532:                 break;
533:             case UrlType::IOS_STREAMING:
534:                 $uri = $cdn->getIosStreamingUri();
535:                 break;
536:         }
537: 
538:         return (isset($uri)) ? Url::factory($uri)->addPath($this->name) : false;
539:     }
540: 
541:     protected static function headerIsValidMetadata($header)
542:     {
543:         $pattern = sprintf('#^%s-%s-Meta-#i', self::GLOBAL_METADATA_PREFIX, self::METADATA_LABEL);
544: 
545:         return preg_match($pattern, $header);
546:     }
547:     
548:     /**
549:      * @return null|string
550:      */
551:     protected function getManifestHeader()
552:     {
553:         $response = $this->getService()
554:             ->getClient()
555:             ->head($this->getUrl())
556:             ->send();
557:             
558:         $manifest = $response->getHeader(HeaderConst::X_OBJECT_MANIFEST);
559:         
560:         $this->setManifest($manifest);
561:         
562:         return $manifest;
563:     }
564: }
565: 
API documentation generated by ApiGen 2.8.0