Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
21fb8fe
chore: added crc to upload part
ShreyasSinha Nov 4, 2025
fa539c0
feat: Added API for list all buckets.
ShreyasSinha Nov 5, 2025
f6b31d3
chore: Adding fixes and tests for ListMultipartUpload
Nov 12, 2025
b900983
fix: Reverting deleted comments
Nov 12, 2025
37bf4ec
fix: Updating version and formatting code
Nov 12, 2025
194284b
fix: update version for ListMulipartUploadsResponse
Nov 12, 2025
de8dc78
fix: adding default for backward compaitbility
Nov 12, 2025
6af874b
fix: call response.disconnect() after resolving resumable upload url …
BenWhitehead Nov 4, 2025
ac9387d
deps: update dependency com.google.cloud:sdk-platform-java-config to …
renovate-bot Nov 7, 2025
9273da0
chore(main): release 2.60.0 (#3360)
release-please[bot] Nov 7, 2025
9f75064
chore(main): release 2.60.1-SNAPSHOT (#3390)
release-please[bot] Nov 10, 2025
6b9562c
chore: Update generation configuration at Sat Nov 8 02:26:58 UTC 202…
cloud-java-bot Nov 10, 2025
57a7abd
chore(deps): update storage release dependencies to v2.60.0 (#3366)
renovate-bot Nov 11, 2025
73b07fa
chore: Update generation configuration at Wed Nov 12 02:30:01 UTC 202…
cloud-java-bot Nov 12, 2025
0701c6b
feat: add UploadPartRequest.crc32c property and requisite plumbing (#…
Dhriti07 Nov 12, 2025
a33268c
fix: Adding & to request uri builder
Nov 13, 2025
5ac47b7
Merge remote-tracking branch 'upstream/main' into list-multipart-upload
Nov 13, 2025
e14725e
fix: using String Builder
Nov 13, 2025
310754d
fix: using MoreObjects helper
Nov 14, 2025
d53c735
Revert "chore: added crc to upload part"
Nov 14, 2025
0ed2945
fix: Making function package private
Nov 14, 2025
e229e73
Merge branch 'main' into list-multipart-upload
Dhriti07 Dec 2, 2025
002fd8b
Resolving comments
Dec 2, 2025
2b2fe9a
fix faliures
Dec 2, 2025
1e162b9
Fixes
Dec 2, 2025
e330520
chore: generate libraries at Tue Dec 2 20:34:14 UTC 2025
cloud-java-bot Dec 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,25 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/** A utility class to parse {@link HttpResponse} and create a {@link UploadPartResponse}. */
/** A utility class to parse checksums from an {@link HttpResponse}. */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed for listing the in progress uploads? If not, can this fix be moved to it's own PR?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, Those changes are moved to another PR and reverted from this PR.

final class ChecksumResponseParser {

private static final String X_GOOG_HASH = "x-goog-hash";

private ChecksumResponseParser() {}

static UploadPartResponse parseUploadResponse(HttpResponse response) {
String eTag = response.getHeaders().getETag();
Map<String, String> hashes = extractHashesFromHeader(response);
return UploadPartResponse.builder().eTag(eTag).md5(hashes.get("md5")).build();
return UploadPartResponse.builder()
.eTag(eTag)
.md5(hashes.get("md5"))
.crc32c(hashes.get("crc32c"))
.build();
}

static CompleteMultipartUploadResponse parseCompleteResponse(HttpResponse response)
Expand All @@ -52,14 +58,18 @@ static CompleteMultipartUploadResponse parseCompleteResponse(HttpResponse respon
}

static Map<String, String> extractHashesFromHeader(HttpResponse response) {
return Optional.ofNullable(response.getHeaders().getFirstHeaderStringValue("x-goog-hash"))
.map(
h ->
Arrays.stream(h.split(","))
.map(s -> s.trim().split("=", 2))
.filter(a -> a.length == 2)
.filter(a -> "crc32c".equalsIgnoreCase(a[0]) || "md5".equalsIgnoreCase(a[0]))
.collect(Collectors.toMap(a -> a[0].toLowerCase(), a -> a[1], (v1, v2) -> v1)))
.orElse(Collections.emptyMap());
List<String> hashHeaders = response.getHeaders().getHeaderStringValues(X_GOOG_HASH);
if (hashHeaders == null || hashHeaders.isEmpty()) {
return Collections.emptyMap();
}

return hashHeaders.stream()
.flatMap(h -> Arrays.stream(h.split(",")))
.map(String::trim)
.filter(s -> !s.isEmpty())
.map(s -> s.split("=", 2))
.filter(a -> a.length == 2)
.filter(a -> "crc32c".equalsIgnoreCase(a[0]) || "md5".equalsIgnoreCase(a[0]))
.collect(Collectors.toMap(a -> a[0].toLowerCase(), a -> a[1], (v1, v2) -> v1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest;
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.ListMultipartUploadsRequest;
import com.google.cloud.storage.multipartupload.model.ListMultipartUploadsResponse;
import com.google.cloud.storage.multipartupload.model.ListPartsRequest;
import com.google.cloud.storage.multipartupload.model.ListPartsResponse;
import com.google.cloud.storage.multipartupload.model.UploadPartRequest;
Expand Down Expand Up @@ -100,6 +102,18 @@ public abstract CompleteMultipartUploadResponse completeMultipartUpload(
@BetaApi
public abstract UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requestBody);

/**
* Lists all multipart uploads in a bucket.
*
* @param request The request object containing the details for listing the multipart uploads.
* @return A {@link ListMultipartUploadsResponse} object containing the list of multipart uploads.
* @since 2.61.0 This new api is in preview and is subject to breaking changes.
*/
@BetaApi
public ListMultipartUploadsResponse listMultipartUploads(ListMultipartUploadsRequest request) {
throw new UnsupportedOperationException("This operation is not yet implemented.");
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are okay breaking this classes API, it's annotated @InternalExtensionOnly, and the constructor is package private preventing subclasses by anything outside our package.

To get clirr to not fail for this we should add an ignore rule to https://github.com/googleapis/java-storage/blob/main/google-cloud-storage/clirr-ignored-differences.xml

  <!-- MultipartUploadClient is @InternalExtensionOnly -->
  <difference>
    <!-- allow new method to be added at any time -->
    <differenceType>7013</differenceType>
    <className>com/google/cloud/storage/MultipartUploadClient</className>
    <method>* *(*)</method>
  </difference>

And keep this method abstract

Suggested change
public ListMultipartUploadsResponse listMultipartUploads(ListMultipartUploadsRequest request) {
throw new UnsupportedOperationException("This operation is not yet implemented.");
}
public abstract ListMultipartUploadsResponse listMultipartUploads(
ListMultipartUploadsRequest request);


/**
* Creates a new instance of {@link MultipartUploadClient}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest;
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.ListMultipartUploadsRequest;
import com.google.cloud.storage.multipartupload.model.ListMultipartUploadsResponse;
import com.google.cloud.storage.multipartupload.model.ListPartsRequest;
import com.google.cloud.storage.multipartupload.model.ListPartsResponse;
import com.google.cloud.storage.multipartupload.model.UploadPartRequest;
Expand Down Expand Up @@ -100,4 +102,12 @@ public UploadPartResponse uploadPart(UploadPartRequest request, RequestBody requ
},
Decoder.identity());
}

@Override
public ListMultipartUploadsResponse listMultipartUploads(ListMultipartUploadsRequest request) {
return retrier.run(
retryAlgorithmManager.idempotent(),
() -> httpRequestManager.sendListMultipartUploadsRequest(uri, request),
Decoder.identity());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import com.google.cloud.storage.multipartupload.model.CompleteMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadRequest;
import com.google.cloud.storage.multipartupload.model.CreateMultipartUploadResponse;
import com.google.cloud.storage.multipartupload.model.ListMultipartUploadsRequest;
import com.google.cloud.storage.multipartupload.model.ListMultipartUploadsResponse;
import com.google.cloud.storage.multipartupload.model.ListPartsRequest;
import com.google.cloud.storage.multipartupload.model.ListPartsResponse;
import com.google.cloud.storage.multipartupload.model.UploadPartRequest;
Expand Down Expand Up @@ -111,6 +113,43 @@ ListPartsResponse sendListPartsRequest(URI uri, ListPartsRequest request) throws
return httpRequest.execute().parseAs(ListPartsResponse.class);
}

ListMultipartUploadsResponse sendListMultipartUploadsRequest(
URI uri, ListMultipartUploadsRequest request) throws IOException {

ImmutableMap.Builder<String, Object> params =
ImmutableMap.<String, Object>builder().put("bucket", request.bucket());
if (request.delimiter() != null) {
params.put("delimiter", request.delimiter());
}
if (request.encodingType() != null) {
params.put("encoding-type", request.encodingType());
}
if (request.keyMarker() != null) {
params.put("key-marker", request.keyMarker());
}
if (request.maxUploads() != null) {
params.put("max-uploads", request.maxUploads());
}
if (request.prefix() != null) {
params.put("prefix", request.prefix());
}
if (request.uploadIdMarker() != null) {
params.put("upload-id-marker", request.uploadIdMarker());
}
String listUri =
UriTemplate.expand(
uri.toString()
+ "{bucket}?uploads{delimiter,encoding-type,key-marker,max-uploads,prefix,upload-id-marker}",
params.build(),
false);
System.out.println(listUri);
HttpRequest httpRequest = requestFactory.buildGetRequest(new GenericUrl(listUri));
httpRequest.getHeaders().putAll(headerProvider.getHeaders());
httpRequest.setParser(objectParser);
httpRequest.setThrowExceptionOnExecuteError(true);
return httpRequest.execute().parseAs(ListMultipartUploadsResponse.class);
}

AbortMultipartUploadResponse sendAbortMultipartUploadRequest(
URI uri, AbortMultipartUploadRequest request) throws IOException {

Expand Down Expand Up @@ -248,7 +287,7 @@ private static String urlEncode(String value) {
*/
private static String formatName(String name) {
// Only lowercase letters, digits, and "-" are allowed
return name.toLowerCase().replaceAll("[^\\w\\d\\-]", "-");
return name.toLowerCase().replaceAll("[^\\w-]", "-");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we need to remove \d from this group?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the comment wasn't updated when the regex was and is now inaccurate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second statement is used because the explicit inclusion of \d in the first statement's exclusion list is redundant, as digits are already covered by \w, making the second expression more concise while having the same effect.

}

private static String formatSemver(String version) {
Expand Down
Loading