Index: library/Zend/Service/Amazon/S3.php =================================================================== --- library/Zend/Service/Amazon/S3.php (revision 22198) +++ library/Zend/Service/Amazon/S3.php (working copy) @@ -503,6 +503,57 @@ } /** + * Copy an object + * + * @param string $sourceObject Source object name + * @param string $destObject Destination object name + * @param array $meta (OPTIONAL) Metadata to apply to desination object. + * Set to null to copy metadata from source object. + * @return boolean + */ + public function copyObject($sourceObject, $destObject, $meta = null) + { + $sourceObject = $this->_fixupObjectName($sourceObject); + $destObject = $this->_fixupObjectName($destObject); + + $headers = (is_array($meta)) ? $meta : array(); + $headers['x-amz-copy-source'] = $sourceObject; + $headers['x-amz-metadata-directive'] = is_null($meta) ? 'COPY' : 'REPLACE'; + + $response = $this->_makeRequest('PUT', $destObject, null, $headers); + + if ($response->getStatus() == 200 && !stristr($response->getBody(), '')) { + return true; + } + + return false; + } + + /** + * Move an object + * + * Performs a copy to dest + verify + remove source + * + * @param string $sourceObject Source object name + * @param string $destObject Destination object name + * @param array $meta (OPTIONAL) Metadata to apply to destination object. + * Set to null to retain existing metadata. + */ + public function moveObject($sourceObject, $destObject, $meta = null) + { + $sourceInfo = $this->getInfo($sourceObject); + + $this->copyObject($sourceObject, $destObject, $meta); + $destInfo = $this->getInfo($destObject); + + if ($sourceInfo['etag'] === $destInfo['etag']) { + return $this->removeObject($sourceObject); + } else { + return false; + } + } + + /** * Make a request to Amazon S3 * * @param string $method Request method @@ -556,10 +607,12 @@ $client->setAuth(false); // Work around buglet in HTTP client - it doesn't clean headers // Remove when ZHC is fixed - $client->setHeaders(array('Content-MD5' => null, - 'Expect' => null, - 'Range' => null, - 'x-amz-acl' => null)); + $client->setHeaders(array('Content-MD5' => null, + 'Expect' => null, + 'Range' => null, + 'x-amz-acl' => null, + 'x-amz-copy-source' => null, + 'x-amz-metadata-directive' => null)); $client->setHeaders($headers); Index: tests/Zend/Service/Amazon/S3/OnlineTest.php =================================================================== --- tests/Zend/Service/Amazon/S3/OnlineTest.php (revision 22198) +++ tests/Zend/Service/Amazon/S3/OnlineTest.php (working copy) @@ -290,6 +290,46 @@ $this->assertFalse($this->_amazon->isObjectAvailable($this->_bucket."/zftestfile")); } + /** + * @depends testCreateBucket + * @depends testCreateObject + */ + public function testCopyObject() + { + $this->_amazon->createBucket($this->_bucket); + $data = "testdata"; + + $this->_amazon->putObject($this->_bucket."/zftest", $data); + $info1 = $this->_amazon->getInfo($this->_bucket."/zftest"); + + $this->_amazon->copyObject($this->_bucket."/zftest", $this->_bucket."/zftest2"); + $this->assertTrue($this->_amazon->isObjectAvailable($this->_bucket."/zftest")); + $this->assertTrue($this->_amazon->isObjectAvailable($this->_bucket."/zftest2")); + $info2 = $this->_amazon->getInfo($this->_bucket."/zftest2"); + + $this->assertEquals($info1['etag'], $info2['etag']); + } + + /** + * @depends testCopyObject + * @depends testRemoveObject + */ + public function testMoveObject() + { + $this->_amazon->createBucket($this->_bucket); + $data = "testdata"; + + $this->_amazon->putObject($this->_bucket."/zftest", $data); + $info1 = $this->_amazon->getInfo($this->_bucket."/zftest"); + + $this->_amazon->moveObject($this->_bucket."/zftest", $this->_bucket."/zftest2"); + $this->assertFalse($this->_amazon->isObjectAvailable($this->_bucket."/zftest")); + $this->assertTrue($this->_amazon->isObjectAvailable($this->_bucket."/zftest2")); + $info2 = $this->_amazon->getInfo($this->_bucket."/zftest2"); + + $this->assertEquals($info1['etag'], $info2['etag']); + } + public function testObjectEncoding() { $this->_amazon->createBucket($this->_bucket);