Skip to content

Commit 73f263f

Browse files
authored
samples: add/modify samples for ranged reads, tail reads of an object and appendable uploads (googleapis#3311)
1 parent 3c395e4 commit 73f263f

File tree

7 files changed

+325
-45
lines changed

7 files changed

+325
-45
lines changed

samples/snippets/src/main/java/com/example/storage/object/StartAppendableObjectUpload.java renamed to samples/snippets/src/main/java/com/example/storage/object/CreateAndWriteAppendableObject.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616

1717
package com.example.storage.object;
1818

19-
// [START storage_start_appendable_object_upload]
19+
// [START storage_create_and_write_appendable_object_upload]
2020

2121
import com.google.cloud.storage.BlobAppendableUpload;
2222
import com.google.cloud.storage.BlobAppendableUpload.AppendableUploadWriteableByteChannel;
2323
import com.google.cloud.storage.BlobAppendableUploadConfig;
2424
import com.google.cloud.storage.BlobAppendableUploadConfig.CloseAction;
2525
import com.google.cloud.storage.BlobId;
2626
import com.google.cloud.storage.BlobInfo;
27+
import com.google.cloud.storage.FlushPolicy;
2728
import com.google.cloud.storage.Storage;
2829
import com.google.cloud.storage.StorageOptions;
2930
import com.google.common.io.ByteStreams;
@@ -33,8 +34,8 @@
3334
import java.nio.file.Paths;
3435
import java.util.Locale;
3536

36-
public class StartAppendableObjectUpload {
37-
public static void startAppendableObjectUpload(
37+
public class CreateAndWriteAppendableObject {
38+
public static void createAndWriteAppendableObject(
3839
String bucketName, String objectName, String filePath) throws Exception {
3940
// The ID of your GCS bucket
4041
// String bucketName = "your-unique-bucket-name";
@@ -49,12 +50,18 @@ public static void startAppendableObjectUpload(
4950
BlobId blobId = BlobId.of(bucketName, objectName);
5051
BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build();
5152

53+
int flushSize = 64 * 1000;
54+
FlushPolicy.MaxFlushSizeFlushPolicy flushPolicy = FlushPolicy.maxFlushSize(flushSize);
5255
BlobAppendableUploadConfig config =
53-
BlobAppendableUploadConfig.of().withCloseAction(CloseAction.CLOSE_WITHOUT_FINALIZING);
56+
BlobAppendableUploadConfig.of()
57+
.withCloseAction(CloseAction.FINALIZE_WHEN_CLOSING)
58+
.withFlushPolicy(flushPolicy);
5459
BlobAppendableUpload uploadSession = storage.blobAppendableUpload(blobInfo, config);
5560
try (AppendableUploadWriteableByteChannel channel = uploadSession.open();
5661
ReadableByteChannel readableByteChannel = FileChannel.open(Paths.get(filePath))) {
5762
ByteStreams.copy(readableByteChannel, channel);
63+
// Since the channel is in a try-with-resources block, channel.close()
64+
// will be implicitly called here, which triggers the finalization.
5865
} catch (IOException ex) {
5966
throw new IOException("Failed to upload to object " + blobId.toGsUtilUri(), ex);
6067
}
@@ -67,4 +74,4 @@ public static void startAppendableObjectUpload(
6774
}
6875
}
6976

70-
// [END storage_start_appendable_object_upload]
77+
// [END storage_create_and_write_appendable_object_upload]
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.storage.object;
18+
19+
// [START storage_open_multiple_objects_ranged_read]
20+
21+
import com.google.api.core.ApiFuture;
22+
import com.google.api.core.ApiFutures;
23+
import com.google.cloud.storage.BlobId;
24+
import com.google.cloud.storage.BlobReadSession;
25+
import com.google.cloud.storage.RangeSpec;
26+
import com.google.cloud.storage.ReadAsFutureBytes;
27+
import com.google.cloud.storage.ReadProjectionConfigs;
28+
import com.google.cloud.storage.Storage;
29+
import com.google.cloud.storage.StorageOptions;
30+
import com.google.common.util.concurrent.MoreExecutors;
31+
import java.util.ArrayList;
32+
import java.util.List;
33+
import java.util.concurrent.TimeUnit;
34+
35+
public class OpenMultipleObjectsRangedRead {
36+
public static void multipleObjectsSingleRangedRead(
37+
String bucketName, List<String> objectNames, long startOffset, int length) throws Exception {
38+
// The ID of your GCS bucket
39+
// String bucketName = "your-unique-bucket-name";
40+
41+
// The ID of your GCS objects to read
42+
// List<String> objectName = Arrays.asList("object-1", "object-2", "object-3");
43+
44+
RangeSpec singleRange = RangeSpec.of(startOffset, length);
45+
ReadAsFutureBytes rangeConfig =
46+
ReadProjectionConfigs.asFutureBytes().withRangeSpec(singleRange);
47+
48+
try (Storage storage = StorageOptions.grpc().build().getService()) {
49+
List<ApiFuture<byte[]>> futuresToWaitOn = new ArrayList<>();
50+
51+
System.out.printf(
52+
"Initiating single ranged read [%d, %d] on %d objects...%n",
53+
startOffset, startOffset + length - 1, objectNames.size());
54+
55+
for (String objectName : objectNames) {
56+
BlobId blobId = BlobId.of(bucketName, objectName);
57+
ApiFuture<BlobReadSession> futureReadSession = storage.blobReadSession(blobId);
58+
59+
ApiFuture<byte[]> readAndCloseFuture =
60+
ApiFutures.transformAsync(
61+
futureReadSession,
62+
(BlobReadSession session) -> {
63+
ApiFuture<byte[]> readFuture = session.readAs(rangeConfig);
64+
65+
readFuture.addListener(
66+
() -> {
67+
try {
68+
session.close();
69+
} catch (java.io.IOException e) {
70+
System.err.println(
71+
"WARN: Background error while closing session: " + e.getMessage());
72+
}
73+
},
74+
MoreExecutors.directExecutor());
75+
return readFuture;
76+
},
77+
MoreExecutors.directExecutor());
78+
79+
futuresToWaitOn.add(readAndCloseFuture);
80+
}
81+
ApiFutures.allAsList(futuresToWaitOn).get(30, TimeUnit.SECONDS);
82+
83+
System.out.println("All concurrent single-ranged read operations are complete.");
84+
}
85+
}
86+
}
87+
// [END storage_open_multiple_objects_ranged_read]
Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package com.example.storage.object;
1818

19-
// [START storage_read_appendable_object_multiple_ranges]
19+
// [START storage_open_object_multiple_ranged_read]
2020

2121
import com.google.api.core.ApiFuture;
2222
import com.google.api.core.ApiFutures;
@@ -30,10 +30,28 @@
3030
import java.util.List;
3131
import java.util.concurrent.TimeUnit;
3232

33-
public class AppendableObjectMultipleRangedRead {
34-
public static void appendableObjectMultipleRangedRead(
33+
public class OpenObjectMultipleRangedRead {
34+
public static void openObjectMultipleRangedRead(
3535
String bucketName, String objectName, long offset1, int length1, long offset2, int length2)
3636
throws Exception {
37+
// The ID of your GCS bucket
38+
// String bucketName = "your-unique-bucket-name";
39+
40+
// The ID of your GCS object
41+
// String objectName = "your-object-name";
42+
43+
// The beginning of the range 1
44+
// long offset = 0
45+
46+
// The maximum number of bytes to read in range 1
47+
// int length = 16;
48+
49+
// The beginning of the range 2
50+
// long offset = 16
51+
52+
// The maximum number of bytes to read in range 2
53+
// int length = 32;
54+
3755
try (Storage storage = StorageOptions.grpc().build().getService()) {
3856
BlobId blobId = BlobId.of(bucketName, objectName);
3957
ApiFuture<BlobReadSession> futureBlobReadSession = storage.blobReadSession(blobId);
@@ -62,4 +80,4 @@ public static void appendableObjectMultipleRangedRead(
6280
}
6381
}
6482

65-
// [END storage_read_appendable_object_multiple_ranges]
83+
// [END storage_open_object_multiple_ranged_read]
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package com.example.storage.object;
1818

19-
// [START storage_read_appendable_object_full]
19+
// [START storage_open_object_read_full_object]
2020

2121
import com.google.api.core.ApiFuture;
2222
import com.google.cloud.storage.BlobId;
@@ -30,9 +30,15 @@
3030
import java.util.Locale;
3131
import java.util.concurrent.TimeUnit;
3232

33-
public class AppendableObjectReadFullObject {
34-
public static void appendableObjectReadFullObject(String bucketName, String objectName)
33+
public class OpenObjectReadFullObject {
34+
public static void openObjectReadFullObject(String bucketName, String objectName)
3535
throws Exception {
36+
// The ID of your GCS bucket
37+
// String bucketName = "your-unique-bucket-name";
38+
39+
// The ID of your GCS object to read
40+
// String objectName = "your-object-name";
41+
3642
try (Storage storage = StorageOptions.grpc().build().getService()) {
3743
BlobId blobId = BlobId.of(bucketName, objectName);
3844
ApiFuture<BlobReadSession> futureBlobReadSession = storage.blobReadSession(blobId);
@@ -60,4 +66,4 @@ public static void appendableObjectReadFullObject(String bucketName, String obje
6066
}
6167
}
6268
}
63-
// [END storage_read_appendable_object_full]
69+
// [END storage_open_object_read_full_object]
Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package com.example.storage.object;
1818

19-
// [START storage_read_appendable_object_single_range]
19+
// [START storage_open_object_single_ranged_read]
2020

2121
import com.google.api.core.ApiFuture;
2222
import com.google.cloud.storage.BlobId;
@@ -27,9 +27,20 @@
2727
import com.google.cloud.storage.StorageOptions;
2828
import java.util.concurrent.TimeUnit;
2929

30-
public class AppendableObjectSingleRangedRead {
31-
public static void appendableObjectSingleRangedRead(
30+
public class OpenObjectSingleRangedRead {
31+
public static void openObjectSingleRangedRead(
3232
String bucketName, String objectName, long offset, int length) throws Exception {
33+
// The ID of your GCS bucket
34+
// String bucketName = "your-unique-bucket-name";
35+
36+
// The ID of your GCS object
37+
// String objectName = "your-object-name";
38+
39+
// The beginning of the range
40+
// long offset = 0
41+
42+
// The maximum number of bytes to read from the object.
43+
// int length = 64;
3344

3445
try (Storage storage = StorageOptions.grpc().build().getService()) {
3546
BlobId blobId = BlobId.of(bucketName, objectName);
@@ -55,4 +66,4 @@ public static void appendableObjectSingleRangedRead(
5566
}
5667
}
5768
}
58-
// [END storage_read_appendable_object_single_range]
69+
// [END storage_open_object_single_ranged_read]

samples/snippets/src/main/java/com/example/storage/object/ResumeAppendableObjectUpload.java renamed to samples/snippets/src/main/java/com/example/storage/object/PauseAndResumeAppendableObjectUpload.java

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* http://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -16,7 +16,7 @@
1616

1717
package com.example.storage.object;
1818

19-
// [START storage_resume_appendable_object_upload]
19+
// [START storage_pause_and_resume_appendable_object_upload]
2020

2121
import com.google.cloud.storage.Blob;
2222
import com.google.cloud.storage.BlobAppendableUpload;
@@ -26,66 +26,82 @@
2626
import com.google.cloud.storage.BlobId;
2727
import com.google.cloud.storage.BlobInfo;
2828
import com.google.cloud.storage.Storage;
29+
import com.google.cloud.storage.StorageChannelUtils;
2930
import com.google.cloud.storage.StorageOptions;
3031
import com.google.common.io.ByteStreams;
3132
import java.io.IOException;
33+
import java.nio.ByteBuffer;
3234
import java.nio.channels.FileChannel;
35+
import java.nio.charset.StandardCharsets;
3336
import java.nio.file.Paths;
3437
import java.util.Locale;
3538

36-
public class ResumeAppendableObjectUpload {
37-
public static void resumeAppendableObjectUpload(
39+
public class PauseAndResumeAppendableObjectUpload {
40+
public static void pauseAndResumeAppendableObjectUpload(
3841
String bucketName, String objectName, String filePath) throws Exception {
3942
// The ID of your GCS bucket
4043
// String bucketName = "your-unique-bucket-name";
4144

42-
// The ID of your GCS unfinalized appendable object
45+
// The ID of your GCS object
4346
// String objectName = "your-object-name";
4447

4548
// The path to the file to upload
4649
// String filePath = "path/to/your/file";
4750

4851
try (Storage storage = StorageOptions.grpc().build().getService()) {
4952
BlobId blobId = BlobId.of(bucketName, objectName);
50-
Blob existingBlob = storage.get(blobId);
51-
BlobInfo blobInfoForTakeover = BlobInfo.newBuilder(existingBlob.getBlobId()).build();
53+
BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build();
54+
55+
// --- Step 1: Initial string write (PAUSE) ---
56+
// Default close action will be CLOSE_WITHOUT_FINALIZING
57+
BlobAppendableUploadConfig initialConfig = BlobAppendableUploadConfig.of();
58+
BlobAppendableUpload initialUploadSession =
59+
storage.blobAppendableUpload(blobInfo, initialConfig);
60+
61+
try (AppendableUploadWriteableByteChannel channel = initialUploadSession.open()) {
62+
String initialData = "Initial data segment.\n";
63+
ByteBuffer buffer = ByteBuffer.wrap(initialData.getBytes(StandardCharsets.UTF_8));
64+
long totalBytesWritten = StorageChannelUtils.blockingEmptyTo(buffer, channel);
65+
channel.flush();
66+
67+
System.out.printf(
68+
Locale.US, "Wrote %d bytes (initial string) in first segment.\n", totalBytesWritten);
69+
} catch (IOException ex) {
70+
throw new IOException("Failed initial upload to object " + blobId.toGsUtilUri(), ex);
71+
}
5272

73+
Blob existingBlob = storage.get(blobId);
5374
long currentObjectSize = existingBlob.getSize();
5475
System.out.printf(
5576
Locale.US,
56-
"Resuming upload for %s. Currently uploaded size: %d bytes\n",
57-
blobId.toGsUtilUri(),
77+
"Initial upload paused. Currently uploaded size: %d bytes\n",
5878
currentObjectSize);
5979

60-
BlobAppendableUploadConfig config =
61-
BlobAppendableUploadConfig.of().withCloseAction(CloseAction.CLOSE_WITHOUT_FINALIZING);
80+
// --- Step 2: Resume upload with file content and finalize ---
81+
// Use FINALIZE_WHEN_CLOSING to ensure the object is finalized on channel closure.
82+
BlobAppendableUploadConfig resumeConfig =
83+
BlobAppendableUploadConfig.of().withCloseAction(CloseAction.FINALIZE_WHEN_CLOSING);
6284
BlobAppendableUpload resumeUploadSession =
63-
storage.blobAppendableUpload(blobInfoForTakeover, config);
85+
storage.blobAppendableUpload(existingBlob.toBuilder().build(), resumeConfig);
86+
6487
try (FileChannel fileChannel = FileChannel.open(Paths.get(filePath));
6588
AppendableUploadWriteableByteChannel channel = resumeUploadSession.open()) {
89+
long bytesToAppend = fileChannel.size();
90+
System.out.printf(
91+
Locale.US,
92+
"Appending the entire file (%d bytes) after the initial string.\n",
93+
bytesToAppend);
6694

67-
if (fileChannel.size() < currentObjectSize) {
68-
throw new IOException(
69-
"Local file is smaller than the already uploaded data. File size: "
70-
+ fileChannel.size()
71-
+ ", Uploaded size: "
72-
+ currentObjectSize);
73-
} else if (fileChannel.size() == currentObjectSize) {
74-
System.out.println("No more data to upload.");
75-
} else {
76-
fileChannel.position(currentObjectSize);
77-
System.out.printf(
78-
Locale.US, "Appending %d bytes\n", fileChannel.size() - currentObjectSize);
79-
ByteStreams.copy(fileChannel, channel);
80-
}
95+
ByteStreams.copy(fileChannel, channel);
8196
}
97+
8298
BlobInfo result = storage.get(blobId);
8399
System.out.printf(
84100
Locale.US,
85-
"Object %s successfully resumed. Total size: %d\n",
101+
"\nObject %s successfully resumed and finalized. Total size: %d bytes\n",
86102
result.getBlobId().toGsUtilUriWithGeneration(),
87103
result.getSize());
88104
}
89105
}
90106
}
91-
// [END storage_resume_appendable_object_upload]
107+
// [END storage_pause_and_resume_appendable_object_upload]

0 commit comments

Comments
 (0)