/packages/zendframework/stream.php
[return to app]1
<?php
2 /**
3 * Zend Framework Amazon S3 PHP stream wrapper class isolated from ZF dependencies to work in Vork
4 *
5 * LICENSE
6 *
7 * This source file is subject to the new BSD license that is bundled
8 * with this package in the file LICENSE.txt.
9 * It is also available through the world-wide-web at this URL:
10 * http://framework.zend.com/license/new-bsd
11 * If you did not receive a copy of the license and are unable to
12 * obtain it through the world-wide-web, please send an email
13 * to license@zend.com so we can send you a copy immediately.
14 *
15 * @category Zend
16 * @package Zend_Service
17 * @subpackage Amazon_S3
18 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
19 * @license http://framework.zend.com/license/new-bsd New BSD License
20 * @version $Id: Stream.php 20096 2010-01-06 02:05:09Z bkarwin $
21 */
22 class Zend_Service_Amazon_S3_Stream {
23 /**
24 * @var boolean Write the buffer on fflush()?
25 */
26 private $_writeBuffer = false;
27
28 /**
29 * @var integer Current read/write position
30 */
31 private $_position = 0;
32
33 /**
34 * @var integer Total size of the object as returned by S3 (Content-length)
35 */
36 private $_objectSize = 0;
37
38 /**
39 * @var string File name to interact with
40 */
41 private $_objectName = null;
42
43 /**
44 * @var string Current read/write buffer
45 */
46 private $_objectBuffer = null;
47
48 /**
49 * @var array Available buckets
50 */
51 private $_bucketList = array();
52
53 /**
54 * @var Zend_Service_Amazon_S3
55 */
56 private $_s3 = null;
57
58 /**
59 * Retrieve client for this stream type
60 *
61 * @param string $path
62 * @return Zend_Service_Amazon_S3
63 */
64 protected function _getS3Client($path) {
65 if ($this->_s3 === null) {
66 $url = explode(':', $path);
67
68 if (!$url) {
69 /**
70 * @see Zend_Service_Amazon_S3_Exception
71 */
72 if (!class_exists('Zend_Exception')) require 'exception.php';
73 throw new Zend_Service_Amazon_S3_Exception("Unable to parse URL $path");
74 }
75
76 $this->_s3 = Zend_Service_Amazon_S3::getWrapperClient($url[0]);
77 if (!$this->_s3) {
78 /**
79 * @see Zend_Service_Amazon_S3_Exception
80 */
81 if (!class_exists('Zend_Exception')) require 'exception.php';
82 throw new Zend_Service_Amazon_S3_Exception("Unknown client for wrapper {$url[0]}");
83 }
84 }
85
86 return $this->_s3;
87 }
88
89 /**
90 * Extract object name from URL
91 *
92 * @param string $path
93 * @return string
94 */
95 protected function _getNamePart($path) {
96 $url = parse_url($path);
97 if ($url['host']) {
98 return !empty($url['path']) ? $url['host'].$url['path'] : $url['host'];
99 }
100
101 return '';
102 }
103
104 /**
105 * Open the stream
106 *
107 * @param string $path
108 * @param string $mode
109 * @param integer $options
110 * @param string $opened_path
111 * @return boolean
112 */
113 public function stream_open($path, $mode, $options, $opened_path) {
114 $name = $this->_getNamePart($path);
115 // If we open the file for writing, just return true. Create the object
116 // on fflush call
117 if (strpbrk($mode, 'wax')) {
118 $this->_objectName = $name;
119 $this->_objectBuffer = null;
120 $this->_objectSize = 0;
121 $this->_position = 0;
122 $this->_writeBuffer = true;
123 $this->_getS3Client($path);
124
125 $this->_optionsArray = array();
126 // Check for options in the URL, starting with a ?
127 if (($saveOptions = strrchr($path, '?')) !== false) {
128 $saveOptions = trim($saveOptions, '?');
129 parse_str($saveOptions, $this->_optionsArray);
130 }
131
132 return true;
133 }
134 else {
135 // Otherwise, just see if the file exists or not
136 $info = $this->_getS3Client($path)->getInfo($name);
137 if ($info) {
138 $this->_objectName = $name;
139 $this->_objectBuffer = null;
140 $this->_objectSize = $info['size'];
141 $this->_position = 0;
142 $this->_writeBuffer = false;
143 $this->_getS3Client($path);
144 return true;
145 }
146 }
147 return false;
148 }
149
150 /**
151 * Close the stream
152 *
153 * @return void
154 */
155 public function stream_close() {
156 $this->_objectName = null;
157 $this->_objectBuffer = null;
158 $this->_objectSize = 0;
159 $this->_position = 0;
160 $this->_writeBuffer = false;
161 unset($this->_s3);
162 }
163
164 /**
165 * Read from the stream
166 *
167 * @param integer $count
168 * @return string
169 */
170 public function stream_read($count) {
171 if (!$this->_objectName) {
172 return false;
173 }
174
175 $range_start = $this->_position;
176 $range_end = $this->_position+$count;
177
178 // Only fetch more data from S3 if we haven't fetched any data yet (postion=0)
179 // OR, the range end position is greater than the size of the current object
180 // buffer AND if the range end position is less than or equal to the object's
181 // size returned by S3
182 if (($this->_position == 0) || (($range_end > strlen($this->_objectBuffer)) && ($range_end <=
$this->_objectSize))) {
183
184 $headers = array(
185 'Range' => "$range_start-$range_end"
186 );
187
188 $response = $this->_s3->_makeRequest('GET', $this->_objectName, null, $headers);
189
190 if ($response->getStatus() == 200) {
191 $this->_objectBuffer .= $response->getBody();
192 }
193 }
194
195 $data = substr($this->_objectBuffer, $this->_position, $count);
196 $this->_position += strlen($data);
197 return $data;
198 }
199
200 /**
201 * Write to the stream
202 *
203 * @param string $data
204 * @return integer
205 */
206 public function stream_write($data) {
207 if (!$this->_objectName) {
208 return 0;
209 }
210 $len = strlen($data);
211 $this->_objectBuffer .= $data;
212 $this->_objectSize += $len;
213 // TODO: handle current position for writing!
214 return $len;
215 }
216
217 /**
218 * End of the stream?
219 *
220 * @return boolean
221 */
222 public function stream_eof() {
223 if (!$this->_objectName) {
224 return true;
225 }
226
227 return ($this->_position >= $this->_objectSize);
228 }
229
230 /**
231 * What is the current read/write position of the stream
232 *
233 * @return integer
234 */
235 public function stream_tell() {
236 return $this->_position;
237 }
238
239 /**
240 * Update the read/write position of the stream
241 *
242 * @param integer $offset
243 * @param integer $whence
244 * @return boolean
245 */
246 public function stream_seek($offset, $whence) {
247 if (!$this->_objectName) {
248 return false;
249 }
250
251 switch ($whence) {
252 case SEEK_CUR:
253 // Set position to current location plus $offset
254 $new_pos = $this->_position + $offset;
255 break;
256 case SEEK_END:
257 // Set position to end-of-file plus $offset
258 $new_pos = $this->_objectSize + $offset;
259 break;
260 case SEEK_SET:
261 default:
262 // Set position equal to $offset
263 $new_pos = $offset;
264 break;
265 }
266 $ret = ($new_pos >= 0 && $new_pos <= $this->_objectSize);
267 if ($ret) {
268 $this->_position = $new_pos;
269 }
270 return $ret;
271 }
272
273 /**
274 * Flush current cached stream data to storage
275 *
276 * @return boolean
277 */
278 public function stream_flush() {
279 // If the stream wasn't opened for writing, just return false
280 if (!$this->_writeBuffer) {
281 return false;
282 }
283
284 $ret = $this->_s3->putObject($this->_objectName, $this->_objectBuffer, $this->_optionsArray);
285
286 $this->_objectBuffer = null;
287
288 return $ret;
289 }
290
291 /**
292 * Returns data array of stream variables
293 *
294 * @return array
295 */
296 public function stream_stat() {
297 if (!$this->_objectName) {
298 return false;
299 }
300
301 $stat = array();
302 $stat['dev'] = 0;
303 $stat['ino'] = 0;
304 $stat['mode'] = 0777;
305 $stat['nlink'] = 0;
306 $stat['uid'] = 0;
307 $stat['gid'] = 0;
308 $stat['rdev'] = 0;
309 $stat['size'] = 0;
310 $stat['atime'] = 0;
311 $stat['mtime'] = 0;
312 $stat['ctime'] = 0;
313 $stat['blksize'] = 0;
314 $stat['blocks'] = 0;
315
316 if(($slash = strchr($this->_objectName, '/')) === false || $slash == strlen($this->_objectName)-1) {
317 /* bucket */
318 $stat['mode'] |= 040000;
319 } else {
320 $stat['mode'] |= 0100000;
321 }
322 $info = $this->_s3->getInfo($this->_objectName);
323 if (!empty($info)) {
324 $stat['size'] = $info['size'];
325 $stat['atime'] = time();
326 $stat['mtime'] = $info['mtime'];
327 }
328
329 return $stat;
330 }
331
332 /**
333 * Attempt to delete the item
334 *
335 * @param string $path
336 * @return boolean
337 */
338 public function unlink($path) {
339 return $this->_getS3Client($path)->removeObject($this->_getNamePart($path));
340 }
341
342 /**
343 * Attempt to rename the item
344 *
345 * @param string $path_from
346 * @param string $path_to
347 * @return boolean False
348 */
349 public function rename($path_from, $path_to) {
350 // TODO: Renaming isn't supported, always return false
351 return false;
352 }
353
354 /**
355 * Create a new directory
356 *
357 * @param string $path
358 * @param integer $mode
359 * @param integer $options
360 * @return boolean
361 */
362 public function mkdir($path, $mode, $options) {
363 return $this->_getS3Client($path)->createBucket(parse_url($path, PHP_URL_HOST));
364 }
365
366 /**
367 * Remove a directory
368 *
369 * @param string $path
370 * @param integer $options
371 * @return boolean
372 */
373 public function rmdir($path, $options) {
374 return $this->_getS3Client($path)->removeBucket(parse_url($path, PHP_URL_HOST));
375 }
376
377 /**
378 * Attempt to open a directory
379 *
380 * @param string $path
381 * @param integer $options
382 * @return boolean
383 */
384 public function dir_opendir($path, $options) {
385
386 if (preg_match('@^([a-z0-9+.]|-)+://$@', $path)) {
387 $this->_bucketList = $this->_getS3Client($path)->getBuckets();
388 }
389 else {
390 $host = parse_url($path, PHP_URL_HOST);
391 $this->_bucketList = $this->_getS3Client($path)->getObjectsByBucket($host);
392 }
393
394 return ($this->_bucketList !== false);
395 }
396
397 /**
398 * Return array of URL variables
399 *
400 * @param string $path
401 * @param integer $flags
402 * @return array
403 */
404 public function url_stat($path, $flags) {
405 $stat = array();
406 $stat['dev'] = 0;
407 $stat['ino'] = 0;
408 $stat['mode'] = 0777;
409 $stat['nlink'] = 0;
410 $stat['uid'] = 0;
411 $stat['gid'] = 0;
412 $stat['rdev'] = 0;
413 $stat['size'] = 0;
414 $stat['atime'] = 0;
415 $stat['mtime'] = 0;
416 $stat['ctime'] = 0;
417 $stat['blksize'] = 0;
418 $stat['blocks'] = 0;
419
420 $name = $this->_getNamePart($path);
421 if(($slash = strchr($name, '/')) === false || $slash == strlen($name)-1) {
422 /* bucket */
423 $stat['mode'] |= 040000;
424 } else {
425 $stat['mode'] |= 0100000;
426 }
427 $info = $this->_getS3Client($path)->getInfo($name);
428
429 if (!empty($info)) {
430 $stat['size'] = $info['size'];
431 $stat['atime'] = time();
432 $stat['mtime'] = $info['mtime'];
433 }
434
435 return $stat;
436 }
437
438 /**
439 * Return the next filename in the directory
440 *
441 * @return string
442 */
443 public function dir_readdir() {
444 $object = current($this->_bucketList);
445 if ($object !== false) {
446 next($this->_bucketList);
447 }
448 return $object;
449 }
450
451 /**
452 * Reset the directory pointer
453 *
454 * @return boolean True
455 */
456 public function dir_rewinddir() {
457 reset($this->_bucketList);
458 return true;
459 }
460
461 /**
462 * Close a directory
463 *
464 * @return boolean True
465 */
466 public function dir_closedir() {
467 $this->_bucketList = array();
468 return true;
469 }
470 }