diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index 202615cdd..82ba17508 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -1051,6 +1051,28 @@ public static BlobListOption delimiter(String delimiter) { return new BlobListOption(StorageRpc.Option.DELIMITER, delimiter); } + /** + * Returns an option to set a startOffset to filter results to objects whose names are + * lexicographically equal to or after startOffset. If endOffset is also set, the objects listed + * have names between startOffset (inclusive) and endOffset (exclusive). + * + * @param startOffset startOffset to filter the results + */ + public static BlobListOption startOffset(String startOffset) { + return new BlobListOption(StorageRpc.Option.START_OFF_SET, startOffset); + } + + /** + * Returns an option to set a endOffset to filter results to objects whose names are + * lexicographically before endOffset. If startOffset is also set, the objects listed have names + * between startOffset (inclusive) and endOffset (exclusive). + * + * @param endOffset endOffset to filter the results + */ + public static BlobListOption endOffset(String endOffset) { + return new BlobListOption(StorageRpc.Option.END_OFF_SET, endOffset); + } + /** * Returns an option to define the billing user project. This option is required by buckets with * `requester_pays` flag enabled to assign operation costs. diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java index ec849281e..d7b7baa0b 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java @@ -354,6 +354,8 @@ public Tuple> list(final String bucket, Map options = + ImmutableMap.of( + StorageRpc.Option.START_OFF_SET, startOffset, StorageRpc.Option.END_OFF_SET, endOffset); + ImmutableList blobInfoList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2); + Tuple> result = + Tuple.of(cursor, Iterables.transform(blobInfoList, BlobInfo.INFO_TO_PB_FUNCTION)); + EasyMock.expect(storageRpcMock.list(BUCKET_NAME1, options)).andReturn(result); + EasyMock.replay(storageRpcMock); + initializeService(); + ImmutableList blobList = ImmutableList.of(expectedBlob1, expectedBlob2); + Page page = + storage.list( + BUCKET_NAME1, + Storage.BlobListOption.startOffset(startOffset), + Storage.BlobListOption.endOffset(endOffset)); + assertEquals(cursor, page.getNextPageToken()); + assertArrayEquals(blobList.toArray(), Iterables.toArray(page.getValues(), Blob.class)); + } + @Test public void testUpdateBucket() { BucketInfo updatedBucketInfo = BUCKET_INFO1.toBuilder().setIndexPage("some-page").build(); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java index fbce81db7..22b1f1d43 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java @@ -993,6 +993,53 @@ public void testListBlobsVersioned() throws ExecutionException, InterruptedExcep } } + @Test + public void testListBlobsWithOffset() throws ExecutionException, InterruptedException { + String bucketName = RemoteStorageHelper.generateBucketName(); + Bucket bucket = + storage.create(BucketInfo.newBuilder(bucketName).setVersioningEnabled(true).build()); + try { + List blobNames = + ImmutableList.of("startOffset_blob1", "startOffset_blob2", "blob3_endOffset"); + BlobInfo blob1 = + BlobInfo.newBuilder(bucket, blobNames.get(0)).setContentType(CONTENT_TYPE).build(); + BlobInfo blob2 = + BlobInfo.newBuilder(bucket, blobNames.get(1)).setContentType(CONTENT_TYPE).build(); + BlobInfo blob3 = + BlobInfo.newBuilder(bucket, blobNames.get(2)).setContentType(CONTENT_TYPE).build(); + + Blob remoteBlob1 = storage.create(blob1); + Blob remoteBlob2 = storage.create(blob2); + Blob remoteBlob3 = storage.create(blob3); + assertNotNull(remoteBlob1); + assertNotNull(remoteBlob2); + assertNotNull(remoteBlob3); + + // Listing blobs without BlobListOptions. + Page page1 = storage.list(bucketName); + assertEquals(3, Iterators.size(page1.iterateAll().iterator())); + + // Listing blobs with startOffset. + Page page2 = + storage.list(bucketName, Storage.BlobListOption.startOffset("startOffset")); + assertEquals(2, Iterators.size(page2.iterateAll().iterator())); + + // Listing blobs with endOffset. + Page page3 = storage.list(bucketName, Storage.BlobListOption.endOffset("endOffset")); + assertEquals(1, Iterators.size(page3.iterateAll().iterator())); + + // Listing blobs with startOffset and endOffset. + Page page4 = + storage.list( + bucketName, + Storage.BlobListOption.startOffset("startOffset"), + Storage.BlobListOption.endOffset("endOffset")); + assertEquals(0, Iterators.size(page4.iterateAll().iterator())); + } finally { + RemoteStorageHelper.forceDelete(storage, bucketName, 5, TimeUnit.SECONDS); + } + } + @Test(timeout = 5000) public void testListBlobsCurrentDirectory() throws InterruptedException { String directoryName = "test-list-blobs-current-directory/";