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