      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\Upload;
     19:
     20: use Guzzle\Batch\BatchBuilder;
     21: use Guzzle\Common\Collection;
     22: use Guzzle\Http\Message\Response;
     23: use Guzzle\Http\Url;
     24: use OpenCloud\ObjectStore\Resource\Container;
     25:
     26: /**
     27:  * Class responsible for migrating the contents of one container to another
     28:  *
     29:  * @package OpenCloud\ObjectStore\Upload
     30:  */
     31: class ContainerMigration
     32: {
     33:     /** @var \Guzzle\Batch\Batch */
     34:     protected $readQueue;
     35:
     36:     /** @var \Guzzle\Batch\Batch */
     37:     protected $writeQueue;
     38:
     39:     /** @var \OpenCloud\ObjectStore\Resource\Container */
     40:     protected $oldContainer;
     41:
     42:     /** @var \OpenCloud\ObjectStore\Resource\Container */
     43:     protected $newContainer;
     44:
     45:     /** @var \Guzzle\Common\Collection */
     46:     protected $options = array();
     47:
     48:     protected $defaults = array(
     49:         'read.batchLimit'  => 1000,
     50:         'read.pageLimit'   => 10000,
     51:         'write.batchLimit' => 100
     52:     );
     53:
     54:     /**
     55:      * @param Container $old     Source container
     56:      * @param Container $new     Target container
     57:      * @param array     $options Options that configure process
     58:      * @return ContainerMigration
     59:      */
     60:     public static function factory(Container $old, Container $new, array $options = array())
     61:     {
     62:         $migration = new self();
     63:
     64:         $migration->setOldContainer($old);
     65:         $migration->setNewContainer($new);
     66:         $migration->setOptions($options);
     67:
     68:         $migration->setupReadQueue();
     69:         $migration->setupWriteQueue();
     70:
     71:         return $migration;
     72:     }
     73:
     74:     /**
     75:      * @param Container $old
     76:      */
     77:     public function setOldContainer(Container $old)
     78:     {
     79:         $this->oldContainer = $old;
     80:     }
     81:
     82:     /**
     83:      * @return Container
     84:      */
     85:     public function getOldContainer()
     86:     {
     87:         return $this->oldContainer;
     88:     }
     89:
     90:     /**
     91:      * @param Container $new
     92:      */
     93:     public function setNewContainer(Container $new)
     94:     {
     95:         $this->newContainer = $new;
     96:     }
     97:
     98:     /**
     99:      * @return Container
    100:      */
    101:     public function getNewContainer()
    102:     {
    103:         return $this->newContainer;
    104:     }
    105:
    106:     /**
    107:      * @param array $options
    108:      */
    109:     public function setOptions(array $options)
    110:     {
    111:         $this->options = Collection::fromConfig($options, $this->defaults);
    112:     }
    113:
    114:     /**
    115:      * @return \Guzzle\Common\Collection
    116:      */
    117:     public function getOptions()
    118:     {
    119:         return $this->options;
    120:     }
    121:
    122:     /**
    123:      * Set the read queue as a {@see \Guzzle\Batch\Batch} queue using the {@see \Guzzle\Batch\BatchBuilder}
    124:      */
    125:     public function setupReadQueue()
    126:     {
    127:         $this->readQueue = BatchBuilder::factory()
    128:             ->transferRequests($this->options->get('read.batchLimit'))
    129:             ->build();
    130:     }
    131:
    132:     /**
    133:      * Set the write queue as a {@see \Guzzle\Batch\Batch} queue using the {@see \Guzzle\Batch\BatchBuilder}
    134:      */
    135:     public function setupWriteQueue()
    136:     {
    137:         $this->writeQueue = BatchBuilder::factory()
    138:             ->transferRequests($this->options->get('write.batchLimit'))
    139:             ->build();
    140:     }
    141:
    142:     /**
    143:      * @return \Guzzle\Http\ClientInterface
    144:      */
    145:     private function getClient()
    146:     {
    147:         return $this->newContainer->getService()->getClient();
    148:     }
    149:
    150:     /**
    151:      * Create a collection of files to be migrated and add them to the read queue
    152:      */
    153:     protected function enqueueGetRequests()
    154:     {
    155:         $files = $this->oldContainer->objectList(array(
    156:             'limit.total' => false,
    157:             'limit.page'  => $this->options->get('read.pageLimit')
    158:         ));
    159:
    160:         foreach ($files as $file) {
    161:             $this->readQueue->add(
    162:                 $this->getClient()->get($file->getUrl())
    163:             );
    164:         }
    165:     }
    166:
    167:     /**
    168:      * Send the read queue (in order to gather more information about individual files)
    169:      *
    170:      * @return array Responses
    171:      */
    172:     protected function sendGetRequests()
    173:     {
    174:         $this->enqueueGetRequests();
    175:
    176:         return $this->readQueue->flush();
    177:     }
    178:
    179:     /**
    180:      * Create a tailored PUT request for each file
    181:      *
    182:      * @param Response $response
    183:      * @return \Guzzle\Http\Message\EntityEnclosingRequestInterface
    184:      */
    185:     protected function createPutRequest(Response $response)
    186:     {
    187:         $segments = Url::factory($response->getEffectiveUrl())->getPathSegments();
    188:         $name = end($segments);
    189:
    190:         // Retrieve content and metadata
    191:         $file = $this->newContainer->dataObject()->setName($name);
    192:         $file->setMetadata($response->getHeaders(), true);
    193:
    194:         return $this->getClient()->put(
    195:             $file->getUrl(),
    196:             $file::stockHeaders($file->getMetadata()->toArray()),
    197:             $response->getBody()
    198:         );
    199:     }
    200:
    201:     /**
    202:      * Initiate the transfer process
    203:      *
    204:      * @return array PUT responses
    205:      */
    206:     public function transfer()
    207:     {
    208:         $requests = $this->sendGetRequests();
    209:         $this->readQueue = null;
    210:
    211:         foreach ($requests as $key => $request) {
    212:             $this->writeQueue->add(
    213:                 $this->createPutRequest($request->getResponse())
    214:             );
    215:             unset($requests[$key]);
    216:         }
    217:
    218:         return $this->writeQueue->flush();
    219:     }
    220: }
    221: