Skip to content

Commit 5a565a7

Browse files
authored
feat: add Storage#downloadTo (#1354)
1 parent cef3d13 commit 5a565a7

File tree

7 files changed

+192
-138
lines changed

7 files changed

+192
-138
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!-- see https://www.mojohaus.org/clirr-maven-plugin/examples/ignored-differences.html -->
33
<differences>
4+
<difference>
5+
<className>com/google/cloud/storage/Storage*</className>
6+
<differenceType>7012</differenceType>
7+
<method>* downloadTo(com.google.cloud.storage.BlobId, java.io.OutputStream, com.google.cloud.storage.Storage$BlobSourceOption[])</method>
8+
</difference>
9+
<difference>
10+
<className>com/google/cloud/storage/Storage*</className>
11+
<differenceType>7012</differenceType>
12+
<method>* downloadTo(com.google.cloud.storage.BlobId, java.nio.file.Path, com.google.cloud.storage.Storage$BlobSourceOption[])</method>
13+
</difference>
414
</differences>

google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919
import static com.google.cloud.storage.Blob.BlobSourceOption.toGetOptions;
2020
import static com.google.cloud.storage.Blob.BlobSourceOption.toSourceOptions;
2121
import static com.google.common.base.Preconditions.checkNotNull;
22-
import static java.util.concurrent.Executors.callable;
2322

24-
import com.google.api.gax.retrying.ResultRetryAlgorithm;
2523
import com.google.api.services.storage.model.StorageObject;
2624
import com.google.auth.ServiceAccountSigner;
2725
import com.google.auth.ServiceAccountSigner.SigningException;
@@ -34,20 +32,17 @@
3432
import com.google.cloud.storage.Storage.SignUrlOption;
3533
import com.google.cloud.storage.spi.v1.StorageRpc;
3634
import com.google.common.io.BaseEncoding;
37-
import com.google.common.io.CountingOutputStream;
3835
import java.io.IOException;
3936
import java.io.ObjectInputStream;
4037
import java.io.OutputStream;
4138
import java.net.URL;
42-
import java.nio.file.Files;
4339
import java.nio.file.Path;
4440
import java.security.Key;
4541
import java.util.Arrays;
4642
import java.util.List;
4743
import java.util.Map;
4844
import java.util.Objects;
4945
import java.util.concurrent.TimeUnit;
50-
import java.util.function.Function;
5146

5247
/**
5348
* An object in Google Cloud Storage. A {@code Blob} object includes the {@code BlobId} instance,
@@ -231,11 +226,7 @@ static Storage.BlobGetOption[] toGetOptions(BlobInfo blobInfo, BlobSourceOption.
231226
* @throws StorageException upon failure
232227
*/
233228
public void downloadTo(Path path, BlobSourceOption... options) {
234-
try (OutputStream outputStream = Files.newOutputStream(path)) {
235-
downloadTo(outputStream, options);
236-
} catch (IOException e) {
237-
throw new StorageException(e);
238-
}
229+
storage.downloadTo(getBlobId(), path, BlobSourceOption.toSourceOptions(this, options));
239230
}
240231

241232
/**
@@ -245,20 +236,7 @@ public void downloadTo(Path path, BlobSourceOption... options) {
245236
* @param options
246237
*/
247238
public void downloadTo(OutputStream outputStream, BlobSourceOption... options) {
248-
final CountingOutputStream countingOutputStream = new CountingOutputStream(outputStream);
249-
final StorageRpc storageRpc = this.options.getStorageRpcV1();
250-
StorageObject pb = getBlobId().toPb();
251-
final Map<StorageRpc.Option, ?> requestOptions = StorageImpl.optionMap(getBlobId(), options);
252-
ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForObjectsGet(pb, requestOptions);
253-
Retrying.run(
254-
this.options,
255-
algorithm,
256-
callable(
257-
() -> {
258-
storageRpc.read(
259-
pb, requestOptions, countingOutputStream.getCount(), countingOutputStream);
260-
}),
261-
Function.identity());
239+
storage.downloadTo(getBlobId(), outputStream, BlobSourceOption.toSourceOptions(this, options));
262240
}
263241

264242
/**

google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.google.cloud.Tuple;
3232
import com.google.cloud.WriteChannel;
3333
import com.google.cloud.storage.Acl.Entity;
34+
import com.google.cloud.storage.Blob.BlobSourceOption;
3435
import com.google.cloud.storage.HmacKey.HmacKeyMetadata;
3536
import com.google.cloud.storage.PostPolicyV4.PostConditionsV4;
3637
import com.google.cloud.storage.PostPolicyV4.PostFieldsV4;
@@ -41,6 +42,7 @@
4142
import com.google.common.io.BaseEncoding;
4243
import java.io.IOException;
4344
import java.io.InputStream;
45+
import java.io.OutputStream;
4446
import java.io.Serializable;
4547
import java.net.URL;
4648
import java.nio.file.Path;
@@ -2672,6 +2674,45 @@ Blob createFrom(
26722674
*/
26732675
ReadChannel reader(BlobId blob, BlobSourceOption... options);
26742676

2677+
/**
2678+
* Downloads the given blob to the given path using specified blob read options.
2679+
*
2680+
* <pre>{@code
2681+
* String bucketName = "my-unique-bucket";
2682+
* String blobName = "my-blob-name";
2683+
* BlobId blobId = BlobId.of(bucketName, blobName);
2684+
* Path destination = Paths.get("my-blob-destination.txt");
2685+
* downloadTo(blobId, destination);
2686+
* // do stuff with destination
2687+
* }</pre>
2688+
*
2689+
* @param blob
2690+
* @param path
2691+
* @param options
2692+
* @throws StorageException upon failure
2693+
*/
2694+
void downloadTo(BlobId blob, Path path, BlobSourceOption... options);
2695+
2696+
/**
2697+
* Downloads the given blob to the given output stream using specified blob read options.
2698+
*
2699+
* <pre>{@code
2700+
* String bucketName = "my-unique-bucket";
2701+
* String blobName = "my-blob-name";
2702+
* BlobId blobId = BlobId.of(bucketName, blobName);
2703+
* Path destination = Paths.get("my-blob-destination.txt");
2704+
* try (OutputStream outputStream = Files.newOutputStream(path)) {
2705+
* downloadTo(blob, outputStream);
2706+
* // do stuff with destination
2707+
* }
2708+
* }</pre>
2709+
*
2710+
* @param blob
2711+
* @param outputStream
2712+
* @param options
2713+
*/
2714+
void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption... options);
2715+
26752716
/**
26762717
* Creates a blob and returns a channel for writing its content. By default any MD5 and CRC32C
26772718
* values in the given {@code blobInfo} are ignored unless requested via the {@code

google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import static com.google.common.base.Preconditions.checkArgument;
3333
import static com.google.common.base.Preconditions.checkState;
3434
import static java.nio.charset.StandardCharsets.UTF_8;
35+
import static java.util.concurrent.Executors.callable;
3536

3637
import com.google.api.gax.paging.Page;
3738
import com.google.api.gax.retrying.ResultRetryAlgorithm;
@@ -49,6 +50,7 @@
4950
import com.google.cloud.Tuple;
5051
import com.google.cloud.WriteChannel;
5152
import com.google.cloud.storage.Acl.Entity;
53+
import com.google.cloud.storage.Blob.BlobSourceOption;
5254
import com.google.cloud.storage.HmacKey.HmacKeyMetadata;
5355
import com.google.cloud.storage.PostPolicyV4.ConditionV4Type;
5456
import com.google.cloud.storage.PostPolicyV4.PostConditionsV4;
@@ -67,10 +69,12 @@
6769
import com.google.common.collect.Maps;
6870
import com.google.common.hash.Hashing;
6971
import com.google.common.io.BaseEncoding;
72+
import com.google.common.io.CountingOutputStream;
7073
import com.google.common.primitives.Ints;
7174
import java.io.ByteArrayInputStream;
7275
import java.io.IOException;
7376
import java.io.InputStream;
77+
import java.io.OutputStream;
7478
import java.io.UnsupportedEncodingException;
7579
import java.net.MalformedURLException;
7680
import java.net.URI;
@@ -543,6 +547,32 @@ public ReadChannel reader(BlobId blob, BlobSourceOption... options) {
543547
return new BlobReadChannel(getOptions(), blob, optionsMap);
544548
}
545549

550+
@Override
551+
public void downloadTo(BlobId blob, Path path, BlobSourceOption... options) {
552+
try (OutputStream outputStream = Files.newOutputStream(path)) {
553+
downloadTo(blob, outputStream, options);
554+
} catch (IOException e) {
555+
throw new StorageException(e);
556+
}
557+
}
558+
559+
@Override
560+
public void downloadTo(BlobId blob, OutputStream outputStream, BlobSourceOption... options) {
561+
final CountingOutputStream countingOutputStream = new CountingOutputStream(outputStream);
562+
final StorageObject pb = blob.toPb();
563+
final Map<StorageRpc.Option, ?> requestOptions = optionMap(blob, options);
564+
ResultRetryAlgorithm<?> algorithm = retryAlgorithmManager.getForObjectsGet(pb, requestOptions);
565+
Retrying.run(
566+
getOptions(),
567+
algorithm,
568+
callable(
569+
() -> {
570+
storageRpc.read(
571+
pb, requestOptions, countingOutputStream.getCount(), countingOutputStream);
572+
}),
573+
Function.identity());
574+
}
575+
546576
@Override
547577
public BlobWriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) {
548578
Tuple<BlobInfo, BlobTargetOption[]> targetOptions = BlobTargetOption.convert(blobInfo, options);

google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java

Lines changed: 0 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616

1717
package com.google.cloud.storage;
1818

19-
import static org.easymock.EasyMock.anyObject;
2019
import static org.easymock.EasyMock.capture;
2120
import static org.easymock.EasyMock.createMock;
22-
import static org.easymock.EasyMock.createNiceMock;
2321
import static org.easymock.EasyMock.createStrictMock;
2422
import static org.easymock.EasyMock.eq;
2523
import static org.easymock.EasyMock.expect;
26-
import static org.easymock.EasyMock.getCurrentArguments;
2724
import static org.easymock.EasyMock.replay;
2825
import static org.easymock.EasyMock.verify;
2926
import static org.junit.Assert.assertArrayEquals;
@@ -32,11 +29,9 @@
3229
import static org.junit.Assert.assertNull;
3330
import static org.junit.Assert.assertSame;
3431
import static org.junit.Assert.assertTrue;
35-
import static org.junit.Assert.fail;
3632

3733
import com.google.api.core.ApiClock;
3834
import com.google.api.gax.retrying.RetrySettings;
39-
import com.google.api.services.storage.model.StorageObject;
4035
import com.google.cloud.ReadChannel;
4136
import com.google.cloud.storage.Acl.Project;
4237
import com.google.cloud.storage.Acl.Project.ProjectRole;
@@ -45,21 +40,16 @@
4540
import com.google.cloud.storage.Blob.BlobSourceOption;
4641
import com.google.cloud.storage.Storage.BlobWriteOption;
4742
import com.google.cloud.storage.Storage.CopyRequest;
48-
import com.google.cloud.storage.spi.v1.StorageRpc;
4943
import com.google.common.collect.ImmutableList;
5044
import com.google.common.collect.ImmutableMap;
5145
import com.google.common.io.BaseEncoding;
52-
import java.io.File;
53-
import java.io.OutputStream;
5446
import java.net.URL;
55-
import java.nio.file.Files;
5647
import java.security.Key;
5748
import java.util.List;
5849
import java.util.Map;
5950
import java.util.concurrent.TimeUnit;
6051
import javax.crypto.spec.SecretKeySpec;
6152
import org.easymock.Capture;
62-
import org.easymock.IAnswer;
6353
import org.junit.After;
6454
import org.junit.Before;
6555
import org.junit.Test;
@@ -612,102 +602,4 @@ public void testBuilder() {
612602
assertNull(blob.getCustomTime());
613603
assertTrue(blob.isDirectory());
614604
}
615-
616-
private StorageRpc prepareForDownload() {
617-
StorageRpc mockStorageRpc = createNiceMock(StorageRpc.class);
618-
expect(storage.getOptions()).andReturn(mockOptions).anyTimes();
619-
replay(storage);
620-
expect(mockOptions.getStorageRpcV1()).andReturn(mockStorageRpc);
621-
expect(mockOptions.getRetrySettings()).andReturn(RETRY_SETTINGS);
622-
expect(mockOptions.getClock()).andReturn(API_CLOCK);
623-
expect(mockOptions.getRetryAlgorithmManager()).andReturn(retryAlgorithmManager).anyTimes();
624-
replay(mockOptions);
625-
blob = new Blob(storage, new BlobInfo.BuilderImpl(BLOB_INFO));
626-
return mockStorageRpc;
627-
}
628-
629-
@Test
630-
public void testDownloadTo() throws Exception {
631-
final byte[] expected = {1, 2};
632-
StorageRpc mockStorageRpc = prepareForDownload();
633-
expect(
634-
mockStorageRpc.read(
635-
anyObject(StorageObject.class),
636-
anyObject(Map.class),
637-
eq(0l),
638-
anyObject(OutputStream.class)))
639-
.andAnswer(
640-
new IAnswer<Long>() {
641-
@Override
642-
public Long answer() throws Throwable {
643-
((OutputStream) getCurrentArguments()[3]).write(expected);
644-
return 2l;
645-
}
646-
});
647-
replay(mockStorageRpc);
648-
File file = File.createTempFile("blob", ".tmp");
649-
blob.downloadTo(file.toPath());
650-
byte actual[] = Files.readAllBytes(file.toPath());
651-
assertArrayEquals(expected, actual);
652-
}
653-
654-
@Test
655-
public void testDownloadToWithRetries() throws Exception {
656-
final byte[] expected = {1, 2};
657-
StorageRpc mockStorageRpc = prepareForDownload();
658-
expect(
659-
mockStorageRpc.read(
660-
anyObject(StorageObject.class),
661-
anyObject(Map.class),
662-
eq(0l),
663-
anyObject(OutputStream.class)))
664-
.andAnswer(
665-
new IAnswer<Long>() {
666-
@Override
667-
public Long answer() throws Throwable {
668-
((OutputStream) getCurrentArguments()[3]).write(expected[0]);
669-
throw new StorageException(504, "error");
670-
}
671-
});
672-
expect(
673-
mockStorageRpc.read(
674-
anyObject(StorageObject.class),
675-
anyObject(Map.class),
676-
eq(1l),
677-
anyObject(OutputStream.class)))
678-
.andAnswer(
679-
new IAnswer<Long>() {
680-
@Override
681-
public Long answer() throws Throwable {
682-
((OutputStream) getCurrentArguments()[3]).write(expected[1]);
683-
return 1l;
684-
}
685-
});
686-
replay(mockStorageRpc);
687-
File file = File.createTempFile("blob", ".tmp");
688-
blob.downloadTo(file.toPath());
689-
byte actual[] = Files.readAllBytes(file.toPath());
690-
assertArrayEquals(expected, actual);
691-
}
692-
693-
@Test
694-
public void testDownloadToWithException() throws Exception {
695-
StorageRpc mockStorageRpc = prepareForDownload();
696-
Exception exception = new IllegalStateException("test");
697-
expect(
698-
mockStorageRpc.read(
699-
anyObject(StorageObject.class),
700-
anyObject(Map.class),
701-
eq(0l),
702-
anyObject(OutputStream.class)))
703-
.andThrow(exception);
704-
replay(mockStorageRpc);
705-
File file = File.createTempFile("blob", ".tmp");
706-
try {
707-
blob.downloadTo(file.toPath());
708-
fail();
709-
} catch (StorageException e) {
710-
assertSame(exception, e.getCause());
711-
}
712-
}
713605
}

0 commit comments

Comments
 (0)