diff --git a/lib/OpenCloud/ObjectStore/Resource/Container.php b/lib/OpenCloud/ObjectStore/Resource/Container.php index 3f47c2227..6a4673e59 100644 --- a/lib/OpenCloud/ObjectStore/Resource/Container.php +++ b/lib/OpenCloud/ObjectStore/Resource/Container.php @@ -184,15 +184,40 @@ public function delete($deleteObjects = false) */ public function deleteAllObjects() { - $requests = array(); + $paths = array(); - $list = $this->objectList(); + $objects = $this->objectList(); - foreach ($list as $object) { - $requests[] = $this->getClient()->delete($object->getUrl()); + foreach ($objects as $object) { + $paths[] = sprintf('/%s/%s', $this->getName(), $object->getName()); } - return $this->getClient()->send($requests); + $this->getService()->batchDelete($paths); + + return $this->waitUntilEmpty(); + } + + /** + * This is a method that makes batch deletions more convenient. It continually + * polls the resource, waiting for its state to change. If the loop exceeds the + * provided timeout, it breaks and returns FALSE. + * + * @param int $secondsToWait The number of seconds to run the loop + * @return bool + */ + public function waitUntilEmpty($secondsToWait = 60, $interval = 1) + { + $endTime = time() + $secondsToWait; + + while (time() < $endTime) { + if ((int) $this->retrieveMetadata()->getProperty('Object-Count') === 0) { + return true; + } + + sleep($interval); + } + + return false; } /** diff --git a/lib/OpenCloud/ObjectStore/Service.php b/lib/OpenCloud/ObjectStore/Service.php index b35cb3c31..a2d01a479 100644 --- a/lib/OpenCloud/ObjectStore/Service.php +++ b/lib/OpenCloud/ObjectStore/Service.php @@ -36,6 +36,7 @@ class Service extends AbstractService { const DEFAULT_NAME = 'cloudFiles'; const DEFAULT_TYPE = 'object-store'; + const BATCH_DELETE_MAX = 10000; /** * This holds the associated CDN service (for Rackspace public cloud) @@ -169,14 +170,55 @@ public function bulkExtract($path = '', $archive, $archiveType = UrlType::TAR_GZ } /** - * This method will delete multiple objects or containers from their account with a single request. + * @deprecated Please use {@see batchDelete()} instead. + */ + public function bulkDelete(array $paths) + { + $this->getLogger()->deprecated(__METHOD__, '::batchDelete()'); + + return $this->executeBatchDeleteRequest($paths); + } + + /** + * Batch delete will delete an array of object paths. By default, + * the API will only accept a maximum of 10,000 object deletions + * per request - so for arrays that exceed this size, it is chunked + * and sent as individual requests. + * + * @param array $paths The objects you want to delete. Each path needs + * be formatted as /{containerName}/{objectName}. If + * you are deleting object_1 and object_2 from the + * photos_container, the array will be: + * + * array( + * '/photos_container/object_1', + * '/photos_container/object_2' + * ) * - * @param array $paths A two-dimensional array of paths: - * array('container_a/file_1', 'container_b/file_78', 'container_c/file_40582') + * @return array The array of responses from the API + * @throws Exception\BulkOperationException + */ + public function batchDelete(array $paths) + { + $chunks = array_chunk($paths, self::BATCH_DELETE_MAX); + + $responses = array(); + + foreach ($chunks as $chunk) { + $responses[] = $this->executeBatchDeleteRequest($chunk); + } + + return $responses; + } + + /** + * Internal method for dispatching single batch delete requests. + * + * @param array $paths * @return \Guzzle\Http\Message\Response * @throws Exception\BulkOperationException */ - public function bulkDelete(array $paths) + private function executeBatchDeleteRequest(array $paths) { $entity = EntityBody::factory(implode(PHP_EOL, $paths)); diff --git a/tests/OpenCloud/Tests/ObjectStore/Resource/ContainerTest.php b/tests/OpenCloud/Tests/ObjectStore/Resource/ContainerTest.php index 8dfd1da5f..db9e04f12 100644 --- a/tests/OpenCloud/Tests/ObjectStore/Resource/ContainerTest.php +++ b/tests/OpenCloud/Tests/ObjectStore/Resource/ContainerTest.php @@ -335,4 +335,20 @@ public function test_Object_Exists_False() { $this->assertFalse($this->container->objectExists('test.foo')); } + + public function test_Waiter_Returns_True_For_Empty_Container() + { + $response = new Response(204, array('X-Container-Object-Count' => 0)); + $this->addMockSubscriber($response); + + $this->assertTrue($this->container->waitUntilEmpty(2, 0)); + } + + public function test_Waiter_Returns_False_On_Timeout() + { + $response = new Response(204, array('X-Container-Object-Count' => 10)); + $this->addMockSubscriber($response); + + $this->assertFalse($this->container->waitUntilEmpty(0.1, 1)); + } } diff --git a/tests/OpenCloud/Tests/ObjectStore/ServiceTest.php b/tests/OpenCloud/Tests/ObjectStore/ServiceTest.php index e5a898712..dfb62e63c 100644 --- a/tests/OpenCloud/Tests/ObjectStore/ServiceTest.php +++ b/tests/OpenCloud/Tests/ObjectStore/ServiceTest.php @@ -25,6 +25,7 @@ */ namespace OpenCloud\Tests\ObjectStore; +use Guzzle\Http\Message\Response; use OpenCloud\ObjectStore\Constants\UrlType; use OpenCloud\ObjectStore\Service; @@ -137,6 +138,19 @@ public function test_Bad_Bulk_Delete() $this->service->bulkDelete(array('nonEmptyContainer')); } + public function test_Batch_Delete_Returns_Array_Of_Responses() + { + $responses = array_fill(0, 2, new Response(200)); + + foreach ($responses as $response) { + $this->addMockSubscriber($response); + } + + $paths = array_fill(0, 15000, '/foo/bar'); + + $this->assertEquals($responses, $this->service->batchDelete($paths)); + } + public function test_Accounts() { $account = $this->service->getAccount();