1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 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: use OpenCloud\ObjectStore\Enum\ReturnType;
34:
35: 36: 37: 38: 39: 40: 41: 42: 43:
44: class Container extends AbstractContainer
45: {
46: const METADATA_LABEL = 'Container';
47:
48: 49: 50: 51: 52: 53:
54: private $cdn;
55:
56: public function __construct(ServiceInterface $service, $data = null)
57: {
58: parent::__construct($service, $data);
59:
60:
61: if (isset($data->count)) {
62: $this->metadata->setProperty('Object-Count', $data->count);
63: }
64: if (isset($data->bytes)) {
65: $this->metadata->setProperty('Bytes-Used', $data->bytes);
66: }
67: }
68:
69: 70: 71: 72: 73: 74: 75:
76: public static function fromResponse(Response $response, ServiceInterface $service)
77: {
78: $self = parent::fromResponse($response, $service);
79:
80: $segments = Url::factory($response->getEffectiveUrl())->getPathSegments();
81: $self->name = end($segments);
82:
83: return $self;
84: }
85:
86: 87: 88: 89: 90: 91:
92: public function getCdn()
93: {
94: if (!$this->isCdnEnabled()) {
95: throw new Exceptions\CdnNotAvailableError(
96: 'Either this container is not CDN-enabled or the CDN is not available'
97: );
98: }
99:
100: return $this->cdn;
101: }
102:
103: 104: 105: 106: 107: 108:
109: public function getObjectCount()
110: {
111: return $this->metadata->getProperty('Object-Count');
112: }
113:
114: 115: 116:
117: public function getBytesUsed()
118: {
119: return $this->metadata->getProperty('Bytes-Used');
120: }
121:
122: 123: 124: 125:
126: public function setCountQuota($value)
127: {
128: $this->metadata->setProperty('Quota-Count', $value);
129:
130: return $this->saveMetadata($this->metadata->toArray());
131: }
132:
133: 134: 135:
136: public function getCountQuota()
137: {
138: return $this->metadata->getProperty('Quota-Count');
139: }
140:
141: 142: 143: 144:
145: public function setBytesQuota($value)
146: {
147: $this->metadata->setProperty('Quota-Bytes', $value);
148:
149: return $this->saveMetadata($this->metadata->toArray());
150: }
151:
152: 153: 154:
155: public function getBytesQuota()
156: {
157: return $this->metadata->getProperty('Quota-Bytes');
158: }
159:
160: public function delete($deleteObjects = false)
161: {
162: if ($deleteObjects === true) {
163:
164: return $this->deleteWithObjects();
165: }
166:
167: try {
168: return $this->getClient()->delete($this->getUrl())->send();
169: } catch (ClientErrorResponseException $e) {
170: if ($e->getResponse()->getStatusCode() == 409) {
171: throw new ContainerException(sprintf(
172: 'The API returned this error: %s. You might have to delete all existing objects before continuing.',
173: (string) $e->getResponse()->getBody()
174: ));
175: } else {
176: throw $e;
177: }
178: }
179: }
180:
181: public function deleteWithObjects($secondsToWait = null)
182: {
183:
184: $numObjects = (int) $this->retrieveMetadata()->getProperty('Object-Count');
185: if (0 === $numObjects) {
186: return $this->delete();
187: }
188:
189:
190:
191: if (null === $secondsToWait) {
192: $secondsToWait = round($numObjects / 2);
193: }
194:
195:
196: $endTime = time() + $secondsToWait;
197: $containerDeleted = false;
198: while ((time() < $endTime) && !$containerDeleted) {
199: $this->deleteAllObjects();
200: try {
201: $response = $this->delete();
202: $containerDeleted = true;
203: } catch (ContainerException $e) {
204:
205: } catch (ClientErrorResponseException $e) {
206: if ($e->getResponse()->getStatusCode() == 404) {
207:
208: $containerDeleted = true;
209: } else {
210: throw $e;
211: }
212: }
213: }
214:
215: if (!$containerDeleted) {
216: throw new ContainerException('Container and all its objects could not be deleted.');
217: }
218:
219: return $response;
220: }
221:
222: 223: 224: 225: 226: 227:
228: public function deleteAllObjects()
229: {
230: $paths = array();
231: $objects = $this->objectList();
232: foreach ($objects as $object) {
233: $paths[] = sprintf('/%s/%s', $this->getName(), $object->getName());
234: }
235: return $this->getService()->batchDelete($paths);
236: }
237:
238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259:
260: public function objectList(array $params = array())
261: {
262: $params['format'] = 'json';
263:
264: return $this->getService()->resourceList('DataObject', $this->getUrl(null, $params), $this);
265: }
266:
267: 268: 269: 270: 271:
272: public function enableLogging()
273: {
274: return $this->saveMetadata($this->appendToMetadata(array(
275: HeaderConst::ACCESS_LOGS => 'True'
276: )));
277: }
278:
279: 280: 281: 282: 283:
284: public function disableLogging()
285: {
286: return $this->saveMetadata($this->appendToMetadata(array(
287: HeaderConst::ACCESS_LOGS => 'False'
288: )));
289: }
290:
291: 292: 293: 294: 295:
296: public function enableCdn($ttl = null)
297: {
298: $headers = array('X-CDN-Enabled' => 'True');
299: if ($ttl) {
300: $headers['X-TTL'] = (int) $ttl;
301: }
302:
303: $this->getClient()->put($this->getCdnService()->getUrl($this->name), $headers)->send();
304: $this->refresh();
305: }
306:
307: 308: 309: 310: 311: 312:
313: public function disableCdn()
314: {
315: $headers = array('X-CDN-Enabled' => 'False');
316:
317: return $this->getClient()
318: ->put($this->getCdnService()->getUrl($this->name), $headers)
319: ->send();
320: }
321:
322: public function refresh($id = null, $url = null)
323: {
324: $headers = $this->createRefreshRequest()->send()->getHeaders();
325: $this->setMetadata($headers, true);
326: }
327:
328: 329: 330: 331: 332: 333:
334: public function dataObject($info = null)
335: {
336: return new DataObject($this, $info);
337: }
338:
339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358:
359: public function getObject($name, array $headers = array())
360: {
361: try {
362: $response = $this->getClient()
363: ->get($this->getUrl($name), $headers)
364: ->send();
365: } catch (BadResponseException $e) {
366: if ($e->getResponse()->getStatusCode() == 404) {
367: throw ObjectNotFoundException::factory($name, $e);
368: }
369: throw $e;
370: }
371:
372: return $this->dataObject()
373: ->populateFromResponse($response)
374: ->setName($name);
375: }
376:
377: 378: 379: 380: 381: 382: 383: 384: 385:
386: public function getPartialObject($name, array $headers = array())
387: {
388: $response = $this->getClient()
389: ->head($this->getUrl($name), $headers)
390: ->send();
391:
392: return $this->dataObject()
393: ->populateFromResponse($response)
394: ->setName($name);
395: }
396:
397: 398: 399: 400: 401: 402: 403:
404: public function objectExists($name)
405: {
406: try {
407:
408: $url = clone $this->getUrl();
409: $url->addPath((string) $name);
410: $this->getClient()->head($url)->send();
411: } catch (ClientErrorResponseException $e) {
412:
413: if ($e->getResponse()->getStatusCode() === 404) {
414: return false;
415: } else {
416: throw $e;
417: }
418: }
419:
420: return true;
421: }
422:
423: 424: 425: 426: 427: 428: 429: 430:
431: public function uploadObject($name, $data, array $headers = array())
432: {
433: $entityBody = EntityBody::factory($data);
434:
435: $url = clone $this->getUrl();
436: $url->addPath($name);
437:
438:
439:
440: $response = $this->getClient()->put($url, $headers, $entityBody)->send();
441:
442: return $this->dataObject()
443: ->populateFromResponse($response)
444: ->setName($name)
445: ->setContent($entityBody);
446: }
447:
448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464:
465: public function uploadObjects(array $files, array $commonHeaders = array(), $returnType = ReturnType::RESPONSE_ARRAY)
466: {
467: $requests = $entities = array();
468:
469: foreach ($files as $entity) {
470: if (empty($entity['name'])) {
471: throw new Exceptions\InvalidArgumentError('You must provide a name.');
472: }
473:
474: if (!empty($entity['path']) && file_exists($entity['path'])) {
475: $body = fopen($entity['path'], 'r+');
476: } elseif (!empty($entity['body'])) {
477: $body = $entity['body'];
478: } else {
479: throw new Exceptions\InvalidArgumentError('You must provide either a readable path or a body');
480: }
481:
482: $entityBody = $entities[] = EntityBody::factory($body);
483:
484:
485: if ($entityBody->getContentLength() >= 5 * Size::GB) {
486: throw new Exceptions\InvalidArgumentError(
487: 'For multiple uploads, you cannot upload more than 5GB per '
488: . ' file. Use the UploadBuilder for larger files.'
489: );
490: }
491:
492:
493:
494: $headers = (isset($entity['headers'])) ? $entity['headers'] : $commonHeaders;
495:
496: $url = clone $this->getUrl();
497: $url->addPath($entity['name']);
498:
499: $requests[] = $this->getClient()->put($url, $headers, $entityBody);
500: }
501:
502: $responses = $this->getClient()->send($requests);
503:
504: if (ReturnType::RESPONSE_ARRAY === $returnType) {
505: foreach ($entities as $entity) {
506: $entity->close();
507: }
508: return $responses;
509: } else {
510:
511: $dataObjects = array();
512: foreach ($responses as $index => $response) {
513: $dataObjects[] = $this->dataObject()
514: ->populateFromResponse($response)
515: ->setName($files[$index]['name'])
516: ->setContent($entities[$index]);
517: }
518: return $dataObjects;
519: }
520: }
521:
522: 523: 524: 525: 526: 527: 528: 529: 530:
531: public function setupObjectTransfer(array $options = array())
532: {
533:
534: if (empty($options['name'])) {
535: throw new Exceptions\InvalidArgumentError('You must provide a name.');
536: }
537:
538:
539: if (!empty($options['path']) && file_exists($options['path'])) {
540: $body = fopen($options['path'], 'r+');
541: } elseif (!empty($options['body'])) {
542: $body = $options['body'];
543: } else {
544: throw new Exceptions\InvalidArgumentError('You must provide either a readable path or a body');
545: }
546:
547:
548: $transfer = TransferBuilder::newInstance()
549: ->setOption('objectName', $options['name'])
550: ->setEntityBody(EntityBody::factory($body))
551: ->setContainer($this);
552:
553:
554: if (!empty($options['metadata'])) {
555: $transfer->setOption('metadata', $options['metadata']);
556: }
557: if (!empty($options['partSize'])) {
558: $transfer->setOption('partSize', $options['partSize']);
559: }
560: if (!empty($options['concurrency'])) {
561: $transfer->setOption('concurrency', $options['concurrency']);
562: }
563: if (!empty($options['progress'])) {
564: $transfer->setOption('progress', $options['progress']);
565: }
566:
567: return $transfer->build();
568: }
569:
570: 571: 572: 573: 574:
575: public function uploadDirectory($path)
576: {
577: $sync = DirectorySync::factory($path, $this);
578: $sync->execute();
579: }
580:
581: public function isCdnEnabled()
582: {
583:
584: if (null === $this->cdn) {
585: $this->refreshCdnObject();
586: }
587: return ($this->cdn instanceof CDNContainer) && $this->cdn->isCdnEnabled();
588: }
589:
590: protected function refreshCdnObject()
591: {
592: try {
593: if (null !== ($cdnService = $this->getService()->getCDNService())) {
594: $cdn = new CDNContainer($cdnService);
595: $cdn->setName($this->name);
596:
597: $response = $cdn->createRefreshRequest()->send();
598:
599: if ($response->isSuccessful()) {
600: $this->cdn = $cdn;
601: $this->cdn->setMetadata($response->getHeaders(), true);
602: }
603: } else {
604: $this->cdn = null;
605: }
606: } catch (ClientErrorResponseException $e) {
607: }
608: }
609: }
610: