Skip to content

Commit f4c92af

Browse files
refactor(Storage): Smplify final chunk detection in resumable uploads
Removes the IsFinalChunk(string) helper method and replaces it with a direct check on HashingStream.EndOfStreamReached. By leveraging the fact that the HashingStream wrapper already observes when 0 bytes are read from the source, we can accurately identify the terminal request without redundant string parsing or range header calculations.
1 parent 9ac0590 commit f4c92af

1 file changed

Lines changed: 14 additions & 56 deletions

File tree

apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/CustomMediaUpload.cs

Lines changed: 14 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
using Google.Apis.Upload;
1818
using System;
1919
using System.IO;
20-
using System.Linq;
2120
using System.Net.Http;
2221
using System.Threading;
2322
using System.Threading.Tasks;
@@ -73,16 +72,10 @@ public Task InterceptAsync(HttpRequestMessage request, CancellationToken cancell
7372
{
7473
return Task.CompletedTask;
7574
}
76-
77-
if (request.Method == System.Net.Http.HttpMethod.Put && request.Content?.Headers.Contains("Content-Range") is true)
75+
if (_hashingStream.EndOfStreamReached)
7876
{
79-
var rangeHeader = request.Content.Headers.GetValues("Content-Range").First();
80-
81-
if (IsFinalChunk(rangeHeader))
82-
{
83-
var calculatedHash = _hashingStream.GetBase64Hash();
84-
request.Headers.TryAddWithoutValidation(GoogleHashHeader, $"crc32c={calculatedHash}");
85-
}
77+
var calculatedHash = _hashingStream.GetBase64Hash();
78+
request.Headers.TryAddWithoutValidation(GoogleHashHeader, $"crc32c={calculatedHash}");
8679
}
8780
return Task.CompletedTask;
8881
}
@@ -102,51 +95,6 @@ private void OnProgressChanged(IUploadProgress progress)
10295
_mediaUpload.ProgressChanged -= OnProgressChanged;
10396
}
10497
}
105-
106-
private bool IsFinalChunk(string rangeHeader)
107-
{
108-
// Expected format: "bytes {start}-{end}/{total}" or "bytes */{total}" for the final request.
109-
// We are interested in the final chunk of a known-size upload.
110-
const string prefix = "bytes ";
111-
if (!rangeHeader.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
112-
{
113-
return false;
114-
}
115-
116-
ReadOnlySpan<char> span = rangeHeader.AsSpan(prefix.Length);
117-
int slashIndex = span.IndexOf('/');
118-
if (slashIndex == -1)
119-
{
120-
return false;
121-
}
122-
123-
var totalSpan = span.Slice(slashIndex + 1);
124-
if (totalSpan.IsEmpty || totalSpan[0] == '*')
125-
{
126-
return false;
127-
}
128-
129-
if (!long.TryParse(totalSpan.ToString(), System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out long totalSize))
130-
{
131-
return false;
132-
}
133-
134-
var rangeSpan = span.Slice(0, slashIndex);
135-
int dashIndex = rangeSpan.IndexOf('-');
136-
if (dashIndex == -1)
137-
{
138-
return false;
139-
}
140-
141-
var endByteSpan = rangeSpan.Slice(dashIndex + 1);
142-
if (!long.TryParse(endByteSpan.ToString(), System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out long endByte))
143-
{
144-
return false;
145-
}
146-
147-
// If endByte is the last byte of the file, it's the final chunk.
148-
return (endByte + 1) == totalSize;
149-
}
15098
}
15199

152100
internal sealed class HashingStream : Stream
@@ -155,6 +103,12 @@ internal sealed class HashingStream : Stream
155103
private readonly Crc32c _hasher;
156104
private long _maxPositionHashed = 0;
157105

106+
///<summary>
107+
/// Returns true if the underlying stream has returned 0 bytes,
108+
/// indicating the end of the stream has been reached.
109+
///</summary>
110+
public bool EndOfStreamReached { get; private set; }
111+
158112
public HashingStream(Stream stream)
159113
{
160114
_stream = stream;
@@ -179,7 +133,11 @@ public override async Task<int> ReadAsync(byte[] buffer, int offset, int count,
179133

180134
private void ProcessBytes(byte[] buffer, int offset, int bytesRead, long startingPos)
181135
{
182-
if (bytesRead <= 0) return;
136+
if (bytesRead <= 0)
137+
{
138+
EndOfStreamReached = true;
139+
return;
140+
}
183141

184142
// Only hash bytes that are beyond the furthest point we've already hashed.
185143
// This handles the rewind and re-read scenario during retries.

0 commit comments

Comments
 (0)