requestOptions = StorageImpl.optionMap(getBlobId(), options);
- ResultRetryAlgorithm> algorithm = retryAlgorithmManager.getForObjectsGet(pb, requestOptions);
- Retrying.run(
- this.options,
- algorithm,
- callable(
- () -> {
- storageRpc.read(
- pb, requestOptions, countingOutputStream.getCount(), countingOutputStream);
- }),
- Function.identity());
+ storage.downloadTo(getBlobId(), outputStream, BlobSourceOption.toSourceOptions(this, options));
}
/**
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 634155f24..d27de9016 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
@@ -31,6 +31,7 @@
import com.google.cloud.Tuple;
import com.google.cloud.WriteChannel;
import com.google.cloud.storage.Acl.Entity;
+import com.google.cloud.storage.Blob.BlobSourceOption;
import com.google.cloud.storage.HmacKey.HmacKeyMetadata;
import com.google.cloud.storage.PostPolicyV4.PostConditionsV4;
import com.google.cloud.storage.PostPolicyV4.PostFieldsV4;
@@ -41,6 +42,7 @@
import com.google.common.io.BaseEncoding;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.io.Serializable;
import java.net.URL;
import java.nio.file.Path;
@@ -2672,6 +2674,45 @@ Blob createFrom(
*/
ReadChannel reader(BlobId blob, BlobSourceOption... options);
+ /**
+ * Downloads the given blob to the given path using specified blob read options.
+ *
+ * {@code
+ * String bucketName = "my-unique-bucket";
+ * String blobName = "my-blob-name";
+ * BlobId blobId = BlobId.of(bucketName, blobName);
+ * Path destination = Paths.get("my-blob-destination.txt");
+ * downloadTo(blobId, destination);
+ * // do stuff with destination
+ * }
+ *
+ * @param blob
+ * @param path
+ * @param options
+ * @throws StorageException upon failure
+ */
+ void downloadTo(BlobId blob, Path path, BlobSourceOption... options);
+
+ /**
+ * Downloads the given blob to the given output stream using specified blob read options.
+ *
+ * {@code
+ * String bucketName = "my-unique-bucket";
+ * String blobName = "my-blob-name";
+ * BlobId blobId = BlobId.of(bucketName, blobName);
+ * Path destination = Paths.get("my-blob-destination.txt");
+ * try (OutputStream outputStream = Files.newOutputStream(path)) {
+ * downloadTo(blob, outputStream);
+ * // do stuff with destination
+ * }
+ * }
+ *
+ * @param blob
+ * @param outputStream
+ * @param options
+ */
+ void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption... options);
+
/**
* Creates a blob and returns a channel for writing its content. By default any MD5 and CRC32C
* values in the given {@code blobInfo} are ignored unless requested via the {@code
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
index 874d997eb..bf372c881 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
@@ -32,6 +32,7 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.concurrent.Executors.callable;
import com.google.api.gax.paging.Page;
import com.google.api.gax.retrying.ResultRetryAlgorithm;
@@ -49,6 +50,7 @@
import com.google.cloud.Tuple;
import com.google.cloud.WriteChannel;
import com.google.cloud.storage.Acl.Entity;
+import com.google.cloud.storage.Blob.BlobSourceOption;
import com.google.cloud.storage.HmacKey.HmacKeyMetadata;
import com.google.cloud.storage.PostPolicyV4.ConditionV4Type;
import com.google.cloud.storage.PostPolicyV4.PostConditionsV4;
@@ -67,10 +69,12 @@
import com.google.common.collect.Maps;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
+import com.google.common.io.CountingOutputStream;
import com.google.common.primitives.Ints;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
@@ -543,6 +547,32 @@ public ReadChannel reader(BlobId blob, BlobSourceOption... options) {
return new BlobReadChannel(getOptions(), blob, optionsMap);
}
+ @Override
+ public void downloadTo(BlobId blob, Path path, BlobSourceOption... options) {
+ try (OutputStream outputStream = Files.newOutputStream(path)) {
+ downloadTo(blob, outputStream, options);
+ } catch (IOException e) {
+ throw new StorageException(e);
+ }
+ }
+
+ @Override
+ public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption... options) {
+ final CountingOutputStream countingOutputStream = new CountingOutputStream(outputStream);
+ final StorageObject pb = blob.toPb();
+ final Map requestOptions = optionMap(blob, options);
+ ResultRetryAlgorithm> algorithm = retryAlgorithmManager.getForObjectsGet(pb, requestOptions);
+ Retrying.run(
+ getOptions(),
+ algorithm,
+ callable(
+ () -> {
+ storageRpc.read(
+ pb, requestOptions, countingOutputStream.getCount(), countingOutputStream);
+ }),
+ Function.identity());
+ }
+
@Override
public BlobWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) {
Tuple targetOptions = BlobTargetOption.convert(blobInfo, options);
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java
index 555349752..97a9b7e23 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java
@@ -16,14 +16,11 @@
package com.google.cloud.storage;
-import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.createStrictMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.getCurrentArguments;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertArrayEquals;
@@ -32,11 +29,9 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import com.google.api.core.ApiClock;
import com.google.api.gax.retrying.RetrySettings;
-import com.google.api.services.storage.model.StorageObject;
import com.google.cloud.ReadChannel;
import com.google.cloud.storage.Acl.Project;
import com.google.cloud.storage.Acl.Project.ProjectRole;
@@ -45,21 +40,16 @@
import com.google.cloud.storage.Blob.BlobSourceOption;
import com.google.cloud.storage.Storage.BlobWriteOption;
import com.google.cloud.storage.Storage.CopyRequest;
-import com.google.cloud.storage.spi.v1.StorageRpc;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.BaseEncoding;
-import java.io.File;
-import java.io.OutputStream;
import java.net.URL;
-import java.nio.file.Files;
import java.security.Key;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.crypto.spec.SecretKeySpec;
import org.easymock.Capture;
-import org.easymock.IAnswer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -612,102 +602,4 @@ public void testBuilder() {
assertNull(blob.getCustomTime());
assertTrue(blob.isDirectory());
}
-
- private StorageRpc prepareForDownload() {
- StorageRpc mockStorageRpc = createNiceMock(StorageRpc.class);
- expect(storage.getOptions()).andReturn(mockOptions).anyTimes();
- replay(storage);
- expect(mockOptions.getStorageRpcV1()).andReturn(mockStorageRpc);
- expect(mockOptions.getRetrySettings()).andReturn(RETRY_SETTINGS);
- expect(mockOptions.getClock()).andReturn(API_CLOCK);
- expect(mockOptions.getRetryAlgorithmManager()).andReturn(retryAlgorithmManager).anyTimes();
- replay(mockOptions);
- blob = new Blob(storage, new BlobInfo.BuilderImpl(BLOB_INFO));
- return mockStorageRpc;
- }
-
- @Test
- public void testDownloadTo() throws Exception {
- final byte[] expected = {1, 2};
- StorageRpc mockStorageRpc = prepareForDownload();
- expect(
- mockStorageRpc.read(
- anyObject(StorageObject.class),
- anyObject(Map.class),
- eq(0l),
- anyObject(OutputStream.class)))
- .andAnswer(
- new IAnswer() {
- @Override
- public Long answer() throws Throwable {
- ((OutputStream) getCurrentArguments()[3]).write(expected);
- return 2l;
- }
- });
- replay(mockStorageRpc);
- File file = File.createTempFile("blob", ".tmp");
- blob.downloadTo(file.toPath());
- byte actual[] = Files.readAllBytes(file.toPath());
- assertArrayEquals(expected, actual);
- }
-
- @Test
- public void testDownloadToWithRetries() throws Exception {
- final byte[] expected = {1, 2};
- StorageRpc mockStorageRpc = prepareForDownload();
- expect(
- mockStorageRpc.read(
- anyObject(StorageObject.class),
- anyObject(Map.class),
- eq(0l),
- anyObject(OutputStream.class)))
- .andAnswer(
- new IAnswer() {
- @Override
- public Long answer() throws Throwable {
- ((OutputStream) getCurrentArguments()[3]).write(expected[0]);
- throw new StorageException(504, "error");
- }
- });
- expect(
- mockStorageRpc.read(
- anyObject(StorageObject.class),
- anyObject(Map.class),
- eq(1l),
- anyObject(OutputStream.class)))
- .andAnswer(
- new IAnswer() {
- @Override
- public Long answer() throws Throwable {
- ((OutputStream) getCurrentArguments()[3]).write(expected[1]);
- return 1l;
- }
- });
- replay(mockStorageRpc);
- File file = File.createTempFile("blob", ".tmp");
- blob.downloadTo(file.toPath());
- byte actual[] = Files.readAllBytes(file.toPath());
- assertArrayEquals(expected, actual);
- }
-
- @Test
- public void testDownloadToWithException() throws Exception {
- StorageRpc mockStorageRpc = prepareForDownload();
- Exception exception = new IllegalStateException("test");
- expect(
- mockStorageRpc.read(
- anyObject(StorageObject.class),
- anyObject(Map.class),
- eq(0l),
- anyObject(OutputStream.class)))
- .andThrow(exception);
- replay(mockStorageRpc);
- File file = File.createTempFile("blob", ".tmp");
- try {
- blob.downloadTo(file.toPath());
- fail();
- } catch (StorageException e) {
- assertSame(exception, e.getCause());
- }
- }
}
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java
index 5619e5f7b..ae3682414 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java
@@ -19,6 +19,11 @@
import static com.google.cloud.storage.SignedUrlEncodingHelper.Rfc3986UriEncode;
import static com.google.cloud.storage.testing.ApiPolicyMatcher.eqApiPolicy;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.getCurrentArguments;
+import static org.easymock.EasyMock.replay;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -27,10 +32,12 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.util.DateTime;
import com.google.api.core.ApiClock;
+import com.google.api.gax.retrying.RetrySettings;
import com.google.api.services.storage.model.Policy.Bindings;
import com.google.api.services.storage.model.StorageObject;
import com.google.api.services.storage.model.TestIamPermissionsResponse;
@@ -53,9 +60,12 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.BaseEncoding;
+import java.io.File;
+import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
+import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
@@ -77,6 +87,7 @@
import javax.crypto.spec.SecretKeySpec;
import org.easymock.Capture;
import org.easymock.EasyMock;
+import org.easymock.IAnswer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -2261,4 +2272,97 @@ public void testBucketLifecycleRules() {
assertEquals(30, lifecycleRule.getCondition().getDaysSinceCustomTime().intValue());
assertNotNull(lifecycleRule.getCondition().getCustomTimeBefore());
}
+
+ @Test
+ public void testDownloadTo() throws Exception {
+ BlobId blob = BlobId.of(BUCKET_NAME1, BLOB_NAME1);
+ storage = options.toBuilder().build().getService();
+ final byte[] expected = {1, 2};
+ EasyMock.expect(
+ storageRpcMock.read(
+ anyObject(StorageObject.class),
+ anyObject(Map.class),
+ eq(0l),
+ anyObject(OutputStream.class)))
+ .andAnswer(
+ new IAnswer() {
+ @Override
+ public Long answer() throws Throwable {
+ ((OutputStream) getCurrentArguments()[3]).write(expected);
+ return 2l;
+ }
+ });
+ EasyMock.replay(storageRpcMock);
+ File file = File.createTempFile("blob", ".tmp");
+ storage.downloadTo(blob, file.toPath());
+ byte actual[] = Files.readAllBytes(file.toPath());
+ assertArrayEquals(expected, actual);
+ }
+
+ @Test
+ public void testDownloadToWithRetries() throws Exception {
+ BlobId blob = BlobId.of(BUCKET_NAME1, BLOB_NAME1);
+ storage =
+ options
+ .toBuilder()
+ .setRetrySettings(RetrySettings.newBuilder().setMaxAttempts(2).build())
+ .build()
+ .getService();
+ final byte[] expected = {1, 2};
+ expect(
+ storageRpcMock.read(
+ anyObject(StorageObject.class),
+ anyObject(Map.class),
+ eq(0l),
+ anyObject(OutputStream.class)))
+ .andAnswer(
+ new IAnswer() {
+ @Override
+ public Long answer() throws Throwable {
+ ((OutputStream) getCurrentArguments()[3]).write(expected[0]);
+ throw new StorageException(504, "error");
+ }
+ });
+ expect(
+ storageRpcMock.read(
+ anyObject(StorageObject.class),
+ anyObject(Map.class),
+ eq(1l),
+ anyObject(OutputStream.class)))
+ .andAnswer(
+ new IAnswer() {
+ @Override
+ public Long answer() throws Throwable {
+ ((OutputStream) getCurrentArguments()[3]).write(expected[1]);
+ return 1l;
+ }
+ });
+ replay(storageRpcMock);
+ File file = File.createTempFile("blob", ".tmp");
+ storage.downloadTo(blob, file.toPath());
+ byte actual[] = Files.readAllBytes(file.toPath());
+ assertArrayEquals(expected, actual);
+ }
+
+ @Test
+ public void testDownloadToWithException() throws Exception {
+ BlobId blob = BlobId.of(BUCKET_NAME1, BLOB_NAME1);
+ storage = options.toBuilder().build().getService();
+ Exception exception = new IllegalStateException("test");
+ expect(
+ storageRpcMock.read(
+ anyObject(StorageObject.class),
+ anyObject(Map.class),
+ eq(0l),
+ anyObject(OutputStream.class)))
+ .andThrow(exception);
+ replay(storageRpcMock);
+ File file = File.createTempFile("blob", ".tmp");
+ try {
+ storage.downloadTo(blob, file.toPath());
+ fail();
+ } catch (StorageException e) {
+ assertSame(exception, e.getCause());
+ }
+ }
}
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 d755d38d2..d5aa1b6d4 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
@@ -883,17 +883,16 @@ public void testGetBlobRawInput() throws IOException {
storage.createFrom(blobInfo, gzippedFile.toPath());
Path rawInputGzippedFile = File.createTempFile("rawinputgzippedfile", ".txt").toPath();
- Blob blob = storage.get(blobId);
- blob.downloadTo(rawInputGzippedFile, Blob.BlobSourceOption.shouldReturnRawInputStream(true));
+ storage.downloadTo(
+ blobId, rawInputGzippedFile, Storage.BlobSourceOption.shouldReturnRawInputStream(true));
assertArrayEquals(
Files.readAllBytes(gzippedFile.toPath()), Files.readAllBytes(rawInputGzippedFile));
Path unzippedFile = File.createTempFile("unzippedfile", ".txt").toPath();
- storage
- .get(blobId)
- .downloadTo(unzippedFile, Blob.BlobSourceOption.shouldReturnRawInputStream(false));
+ storage.downloadTo(
+ blobId, unzippedFile, Storage.BlobSourceOption.shouldReturnRawInputStream(false));
assertArrayEquals("hello world".getBytes(), Files.readAllBytes(unzippedFile));
}
@@ -3686,7 +3685,7 @@ public void testUploadFromDownloadTo() throws Exception {
assertEquals(BLOB_BYTE_CONTENT.length, (long) blob.getSize());
Path tempFileTo = Files.createTempFile("ITStorageTest_", ".tmp");
- storage.get(blobId).downloadTo(tempFileTo);
+ storage.downloadTo(blobId, tempFileTo);
byte[] readBytes = Files.readAllBytes(tempFileTo);
assertArrayEquals(BLOB_BYTE_CONTENT, readBytes);
}