Skip to content

Commit 8bf9f95

Browse files
committed
feat(gax): add ResumableUploadClient and supporting classes
1 parent 84b2069 commit 8bf9f95

8 files changed

Lines changed: 653 additions & 0 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
* * Neither the name of Google LLC nor the names of its
15+
* contributors may be used to endorse or promote products derived from
16+
* this software without specific prior written permission.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
package com.google.api.gax.resumableupload;
31+
32+
import com.google.api.core.InternalApi;
33+
import com.google.auto.value.AutoValue;
34+
import com.google.common.base.Preconditions;
35+
import com.google.protobuf.ByteString;
36+
import org.jspecify.annotations.NullMarked;
37+
38+
/** Request parameters for uploading an individual payload chunk. */
39+
@InternalApi
40+
@NullMarked
41+
@AutoValue
42+
public abstract class ChunkUploadRequest {
43+
44+
/** Returns the upload session URI. */
45+
public abstract String getUploadUrl();
46+
47+
/** Returns the byte payload to transmit in this chunk. */
48+
public abstract ByteString getPayload();
49+
50+
/** Returns the byte offset within the total stream where this chunk begins. */
51+
public abstract long getOffset();
52+
53+
/** Returns the total length of the upload payload in bytes, or -1 if unknown. */
54+
public abstract long getTotalLength();
55+
56+
/** Returns whether this chunk is the final chunk of the upload. */
57+
public abstract boolean isFinal();
58+
59+
public abstract Builder toBuilder();
60+
61+
public static Builder builder() {
62+
return new AutoValue_ChunkUploadRequest.Builder()
63+
.setOffset(0L)
64+
.setTotalLength(-1L)
65+
.setIsFinal(false);
66+
}
67+
68+
public static ChunkUploadRequest create(
69+
String uploadUrl, ByteString payload, long offset, long totalLength, boolean isFinal) {
70+
return builder()
71+
.setUploadUrl(uploadUrl)
72+
.setPayload(payload)
73+
.setOffset(offset)
74+
.setTotalLength(totalLength)
75+
.setIsFinal(isFinal)
76+
.build();
77+
}
78+
79+
@AutoValue.Builder
80+
public abstract static class Builder {
81+
public abstract Builder setUploadUrl(String uploadUrl);
82+
83+
public abstract Builder setPayload(ByteString payload);
84+
85+
public abstract Builder setOffset(long offset);
86+
87+
public abstract Builder setTotalLength(long totalLength);
88+
89+
public abstract Builder setIsFinal(boolean isFinal);
90+
91+
abstract ChunkUploadRequest autoBuild();
92+
93+
public ChunkUploadRequest build() {
94+
ChunkUploadRequest request = autoBuild();
95+
Preconditions.checkArgument(request.getOffset() >= 0, "offset must be non-negative");
96+
Preconditions.checkArgument(request.getTotalLength() >= -1, "totalLength must be >= -1");
97+
if (request.getTotalLength() >= 0) {
98+
Preconditions.checkArgument(
99+
request.getOffset() <= request.getTotalLength(), "offset must be <= totalLength");
100+
}
101+
return request;
102+
}
103+
}
104+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
* * Neither the name of Google LLC nor the names of its
15+
* contributors may be used to endorse or promote products derived from
16+
* this software without specific prior written permission.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
package com.google.api.gax.resumableupload;
31+
32+
import com.google.api.core.InternalApi;
33+
import com.google.auto.value.AutoValue;
34+
import com.google.common.base.Preconditions;
35+
import org.jspecify.annotations.NullMarked;
36+
import org.jspecify.annotations.Nullable;
37+
38+
/** Response received after uploading an individual payload chunk. */
39+
@InternalApi
40+
@NullMarked
41+
@AutoValue
42+
public abstract class ChunkUploadResponse {
43+
44+
/** Returns the byte offset confirmed by the server as successfully received. */
45+
public abstract long getCommittedOffset();
46+
47+
/** Returns whether the entire upload has completed. */
48+
public abstract boolean isComplete();
49+
50+
/** Returns the raw server response body (present on completion), or {@code null} if ongoing. */
51+
public abstract @Nullable String getResponseBody();
52+
53+
public static ChunkUploadResponse create(
54+
long committedOffset, boolean isComplete, @Nullable String responseBody) {
55+
Preconditions.checkArgument(committedOffset >= 0, "committedOffset must be non-negative");
56+
return new AutoValue_ChunkUploadResponse(committedOffset, isComplete, responseBody);
57+
}
58+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
* * Neither the name of Google LLC nor the names of its
15+
* contributors may be used to endorse or promote products derived from
16+
* this software without specific prior written permission.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
package com.google.api.gax.resumableupload;
31+
32+
import com.google.api.core.InternalApi;
33+
import com.google.auto.value.AutoValue;
34+
import com.google.common.base.Preconditions;
35+
import org.jspecify.annotations.NullMarked;
36+
37+
/** Request parameters for querying the current upload status of a session. */
38+
@InternalApi
39+
@NullMarked
40+
@AutoValue
41+
public abstract class QueryStatusRequest {
42+
43+
/** Returns the upload session URI. */
44+
public abstract String getUploadUrl();
45+
46+
/** Returns the total length of the upload payload in bytes, or -1 if unknown. */
47+
public abstract long getTotalLength();
48+
49+
public static QueryStatusRequest create(String uploadUrl, long totalLength) {
50+
Preconditions.checkArgument(totalLength >= -1, "totalLength must be >= -1");
51+
return new AutoValue_QueryStatusRequest(uploadUrl, totalLength);
52+
}
53+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
* * Neither the name of Google LLC nor the names of its
15+
* contributors may be used to endorse or promote products derived from
16+
* this software without specific prior written permission.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
package com.google.api.gax.resumableupload;
31+
32+
import com.google.api.core.InternalApi;
33+
import com.google.auto.value.AutoValue;
34+
import com.google.common.base.Preconditions;
35+
import org.jspecify.annotations.NullMarked;
36+
import org.jspecify.annotations.Nullable;
37+
38+
/** Response received from querying the server for current upload progress. */
39+
@InternalApi
40+
@NullMarked
41+
@AutoValue
42+
public abstract class QueryStatusResponse {
43+
44+
/** Returns the byte offset confirmed by the server as successfully received. */
45+
public abstract long getCommittedOffset();
46+
47+
/** Returns whether the entire upload has completed. */
48+
public abstract boolean isComplete();
49+
50+
/** Returns the raw server response body (present on completion), or {@code null} if ongoing. */
51+
public abstract @Nullable String getResponseBody();
52+
53+
public static QueryStatusResponse create(long committedOffset) {
54+
return create(committedOffset, false, null);
55+
}
56+
57+
public static QueryStatusResponse create(
58+
long committedOffset, boolean isComplete, @Nullable String responseBody) {
59+
Preconditions.checkArgument(committedOffset >= 0, "committedOffset must be non-negative");
60+
return new AutoValue_QueryStatusResponse(committedOffset, isComplete, responseBody);
61+
}
62+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
* * Neither the name of Google LLC nor the names of its
15+
* contributors may be used to endorse or promote products derived from
16+
* this software without specific prior written permission.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
package com.google.api.gax.resumableupload;
31+
32+
import com.google.api.core.InternalApi;
33+
import com.google.api.gax.rpc.UnaryCallable;
34+
import org.jspecify.annotations.NullMarked;
35+
36+
/** An interface for executing low-level resumable upload operations. */
37+
@InternalApi
38+
@NullMarked
39+
public interface ResumableUploadClient {
40+
41+
/** Returns a {@link UnaryCallable} to initiate a resumable upload session. */
42+
UnaryCallable<StartUploadRequest, ResumableUploadSession> startUploadCallable();
43+
44+
/** Returns a {@link UnaryCallable} to transmit an individual chunk. */
45+
UnaryCallable<ChunkUploadRequest, ChunkUploadResponse> uploadChunkCallable();
46+
47+
/** Returns a {@link UnaryCallable} to query the server for current upload status. */
48+
UnaryCallable<QueryStatusRequest, QueryStatusResponse> queryStatusCallable();
49+
}

0 commit comments

Comments
 (0)