1: <?php
2: /**
3: * PHP OpenCloud library.
4: *
5: * @copyright Copyright 2013 Rackspace US, Inc. See COPYING for licensing information.
6: * @license https://www.apache.org/licenses/LICENSE-2.0 Apache 2.0
7: * @version 1.6.0
8: * @author Glen Campbell <glen.campbell@rackspace.com>
9: * @author Jamie Hannaford <jamie.hannaford@rackspace.com>
10: */
11:
12: namespace OpenCloud\ObjectStore\Resource;
13:
14: use OpenCloud\Common\Service as AbstractService;
15: use OpenCloud\Common\Lang;
16: use OpenCloud\Common\Exceptions;
17: use OpenCloud\ObjectStore\AbstractService as AbstractObjectService;
18:
19: /**
20: * A container that has been CDN-enabled. Each CDN-enabled container has a unique
21: * Uniform Resource Locator (URL) that can be combined with its object names and
22: * openly distributed in web pages, emails, or other applications.
23: */
24: class CDNContainer extends AbstractStorageObject
25: {
26: /**
27: * The name of the container.
28: *
29: * The only restrictions on container names is that they cannot contain a
30: * forward slash (/) and must be less than 256 bytes in length. Please note
31: * that the length restriction applies to the name after it has been URL
32: * encoded. For example, a container named Course Docs would be URL encoded
33: * as Course%20Docs - which is 13 bytes in length rather than the expected 11.
34: *
35: * @var string
36: */
37: public $name;
38:
39: /**
40: * Count of how many objects exist in the container.
41: *
42: * @var int
43: */
44: public $count = 0;
45:
46: /**
47: * The total bytes used in the container.
48: *
49: * @var int
50: */
51: public $bytes = 0;
52:
53: /**
54: * The service object.
55: *
56: * @var AbstractService
57: */
58: private $service;
59:
60: /**
61: * URL of the container.
62: *
63: * @var string
64: */
65: private $containerUrl;
66:
67: /**
68: * Creates the container object
69: *
70: * Creates a new container object or, if the $cdata object is a string,
71: * retrieves the named container from the object store. If $cdata is an
72: * array or an object, then its values are used to set this object.
73: *
74: * @param OpenCloud\ObjectStore $service - the ObjectStore service
75: * @param mixed $cdata - if supplied, the name of the object
76: */
77: public function __construct(AbstractService $service, $cdata = null)
78: {
79: $this->getLogger()->info('Initializing CDN Container Service...');
80:
81: parent::__construct();
82:
83: $this->service = $service;
84:
85: // Populate data if set
86: $this->populate($cdata);
87: }
88:
89: /**
90: * Allow other objects to know what the primary key is.
91: *
92: * @return string
93: */
94: public function primaryKeyField()
95: {
96: return 'name';
97: }
98:
99: /**
100: * Returns the Service associated with the Container
101: */
102: public function getService()
103: {
104: return $this->service;
105: }
106:
107: /**
108: * Returns the URL of the container
109: *
110: * @return string
111: * @param string $subresource not used; required for compatibility
112: * @throws NoNameError
113: */
114: public function url($subresource = '')
115: {
116: if (strlen($this->name) == 0) {
117: throw new Exceptions\NoNameError(
118: Lang::translate('Container does not have an identifier')
119: );
120: }
121:
122: return Lang::noslash($this->getService()->url(rawurlencode($this->name)));
123: }
124:
125: /**
126: * Creates a new container with the specified attributes
127: *
128: * @param array $params array of parameters
129: * @return boolean TRUE on success; FALSE on failure
130: * @throws ContainerCreateError
131: */
132: public function create($params = array())
133: {
134: // Populate object and check container name
135: $this->populate($params);
136: $this->isValidName($this->name);
137:
138: // Dispatch
139: $this->containerUrl = $this->url();
140: $response = $this->getService()->request($this->url(), 'PUT', $this->metadataHeaders());
141:
142: // Check return code
143: // @codeCoverageIgnoreStart
144: if ($response->httpStatus() > 202) {
145: throw new Exceptions\ContainerCreateError(sprintf(
146: Lang::translate('Problem creating container [%s] status [%d] response [%s]'),
147: $this->url(),
148: $response->httpStatus(),
149: $response->httpBody()
150: ));
151: }
152: // @codeCoverageIgnoreEnd
153:
154: return true;
155: }
156:
157: /**
158: * Updates the metadata for a container
159: *
160: * @return boolean TRUE on success; FALSE on failure
161: * @throws ContainerCreateError
162: */
163: public function update()
164: {
165: $response = $this->getService()->request($this->url(), 'POST', $this->metadataHeaders());
166:
167: // check return code
168: // @codeCoverageIgnoreStart
169: if ($response->httpStatus() > 204) {
170: throw new Exceptions\ContainerCreateError(sprintf(
171: Lang::translate('Problem updating container [%s] status [%d] response [%s]'),
172: $this->Url(),
173: $response->httpStatus(),
174: $response->httpBody()
175: ));
176: }
177: // @codeCoverageIgnoreEnd
178:
179: return true;
180: }
181:
182: /**
183: * Deletes the specified container
184: *
185: * @return boolean TRUE on success; FALSE on failure
186: * @throws ContainerDeleteError
187: */
188: public function delete()
189: {
190: $response = $this->getService()->request($this->url(), 'DELETE');
191:
192: // validate the response code
193: // @codeCoverageIgnoreStart
194: if ($response->httpStatus() == 404) {
195: throw new Exceptions\ContainerNotFoundError(sprintf(
196: Lang::translate('Container [%s] not found'),
197: $this->name
198: ));
199: }
200:
201: if ($response->httpStatus() == 409) {
202: throw new Exceptions\ContainerNotEmptyError(sprintf(
203: Lang::translate('Container [%s] must be empty before deleting'),
204: $this->name
205: ));
206: }
207:
208: if ($response->httpStatus() >= 300) {
209: throw new Exceptions\ContainerDeleteError(sprintf(
210: Lang::translate('Problem deleting container [%s] status [%d] response [%s]'),
211: $this->url(),
212: $response->httpStatus(),
213: $response->httpBody()
214: ));
215: return false;
216: }
217: // @codeCoverageIgnoreEnd
218:
219: return true;
220: }
221:
222: /**
223: * Loads the object from the service
224: *
225: * @return void
226: */
227: public function refresh($name = null, $url = null)
228: {
229: $response = $this->getService()->request(
230: $this->url($name), 'HEAD', array('Accept' => '*/*')
231: );
232:
233: // validate the response code
234: // @codeCoverageIgnoreStart
235: if ($response->HttpStatus() == 404) {
236: throw new Exceptions\ContainerNotFoundError(sprintf(
237: 'Container [%s] (%s) not found',
238: $this->name,
239: $this->url()
240: ));
241: }
242:
243: if ($response->HttpStatus() >= 300) {
244: throw new Exceptions\HttpError(sprintf(
245: 'Error retrieving Container, status [%d] response [%s]',
246: $response->httpStatus(),
247: $response->httpBody()
248: ));
249: }
250:
251: // check for headers (not metadata)
252: foreach($response->headers() as $header => $value) {
253: switch($header) {
254: case 'X-Container-Object-Count':
255: $this->count = $value;
256: break;
257: case 'X-Container-Bytes-Used':
258: $this->bytes = $value;
259: break;
260: }
261: }
262: // @codeCoverageIgnoreEnd
263:
264: // parse the returned object
265: $this->getMetadata($response);
266: }
267:
268: /**
269: * Validates that the container name is acceptable
270: *
271: * @param string $name the container name to validate
272: * @return boolean TRUE if ok; throws an exception if not
273: * @throws ContainerNameError
274: */
275: public function isValidName($name)
276: {
277: if (strlen($name) == 0) {
278: throw new Exceptions\ContainerNameError(
279: 'Container name cannot be blank'
280: );
281: }
282:
283: if (strpos($name, '/') !== false) {
284: throw new Exceptions\ContainerNameError(
285: 'Container name cannot contain "/"'
286: );
287: }
288:
289: if (strlen($name) > AbstractObjectService::MAX_CONTAINER_NAME_LEN) {
290: throw new Exceptions\ContainerNameError(
291: 'Container name is too long'
292: );
293: }
294:
295: return true;
296: }
297:
298: }
299: