@@ -70,6 +70,7 @@ final class ResumableUploadChunkCoordinator<ResponseT> {
7070 private final String uploadUrl ;
7171 private final RewindableStreamBuffer buffer ;
7272 private final ApiCallContext callContext ;
73+ private final ResumableUploadProgressTracker progressTracker ;
7374 private final SettableApiFuture <ResponseT > result = SettableApiFuture .create ();
7475 private volatile @ Nullable ApiFuture <?> currentChunkFuture ;
7576
@@ -79,14 +80,16 @@ final class ResumableUploadChunkCoordinator<ResponseT> {
7980 String uploadUrl ,
8081 InputStream payload ,
8182 int chunkSize ,
82- ApiCallContext callContext ) {
83+ ApiCallContext callContext ,
84+ ResumableUploadProgressTracker progressTracker ) {
8385 this .uploadChunkCallable =
8486 checkNotNull (uploadChunkCallable , "uploadChunkCallable must not be null" );
8587 this .queryStatusCallable =
8688 checkNotNull (queryStatusCallable , "queryStatusCallable must not be null" );
8789 this .uploadUrl = checkNotNull (uploadUrl , "uploadUrl must not be null" );
8890 checkNotNull (payload , "payload must not be null" );
8991 this .callContext = checkNotNull (callContext , "callContext must not be null" );
92+ this .progressTracker = checkNotNull (progressTracker , "progressTracker must not be null" );
9093 this .buffer = new RewindableStreamBuffer (payload , chunkSize , uploadUrl );
9194 }
9295
@@ -136,7 +139,7 @@ public void onSuccess(ChunkUploadResponse<ResponseT> response) {
136139 return ;
137140 }
138141 if (response .getUploadStatus () == ResumableUploadStatus .UNKNOWN ) {
139- recover ();
142+ recover (null );
140143 } else {
141144 onChunkUploaded (response );
142145 }
@@ -150,7 +153,7 @@ public void onFailure(Throwable t) {
150153 Category category =
151154 ResumableUploadErrorClassifier .classify (t , ResumableUploadCommand .UPLOAD );
152155 if (category == Category .RECOVERABLE ) {
153- recover ();
156+ recover (t );
154157 } else {
155158 // Category.TRANSIENT errors reaching here have already exhausted their retry budget
156159 // in the underlying RetryingCallable and become fatal per protocol specification.
@@ -164,7 +167,14 @@ public void onFailure(Throwable t) {
164167 }
165168 }
166169
167- private void recover () {
170+ /**
171+ * Queries the session for the server's committed offset and resumes transmission from it.
172+ *
173+ * @param cause the error that triggered recovery, or null if the server acknowledged the chunk
174+ * without an upload status header
175+ */
176+ private void recover (@ Nullable Throwable cause ) {
177+ progressTracker .onRecovering ();
168178 try {
169179 // Dispatch the query status call and register the in-flight future for cancellation.
170180 ApiFuture <QueryStatusResponse <ResponseT >> queryFuture =
@@ -228,22 +238,29 @@ private void handleQueryResponse(QueryStatusResponse<ResponseT> queryResponse)
228238 "Incomplete query status response did not include a committed offset for upload URL: "
229239 + uploadUrl );
230240 }
241+ progressTracker .onOffsetReceived (committedOffset );
231242 buffer .realignTo (committedOffset );
232243 dispatchCurrentChunk ();
233244 }
234245
235246 private void onChunkUploaded (ChunkUploadResponse <ResponseT > response ) {
236- long nextOffset = buffer .getBufferBaseOffset () + buffer .getPayloadLength ();
237- if (response .getUploadStatus () == ResumableUploadStatus .FINAL ) {
238- result .set (response .getResponse ());
239- } else if (buffer .isFinal ()) {
240- result .setException (
241- new IllegalStateException (
242- "Upload stream ended and final chunk was transmitted, but server returned"
243- + " incomplete status for upload URL: "
244- + uploadUrl ));
245- } else {
246- chunkExecutor .execute (() -> transmitChunk (nextOffset ));
247+ try {
248+ long nextOffset = buffer .getBufferBaseOffset () + buffer .getPayloadLength ();
249+ if (response .getUploadStatus () == ResumableUploadStatus .FINAL ) {
250+ progressTracker .onChunkUploaded (nextOffset );
251+ result .set (response .getResponse ());
252+ } else if (buffer .isFinal ()) {
253+ result .setException (
254+ new IllegalStateException (
255+ "Upload stream ended and final chunk was transmitted, but server returned"
256+ + " incomplete status for upload URL: "
257+ + uploadUrl ));
258+ } else {
259+ progressTracker .onChunkUploaded (nextOffset );
260+ chunkExecutor .execute (() -> transmitChunk (nextOffset ));
261+ }
262+ } catch (Throwable t ) {
263+ result .setException (t );
247264 }
248265 }
249266
0 commit comments