Skip to content

Commit fe2c29d

Browse files
etag lock once again passed down to wire (Azure#24731)
* etag lock once again passed down to wire * don't modify options in DownloadStreamingInternal * deep copy constructors * will no longer clone default * added null coalesce now that clone can return null Co-authored-by: jschrepp-MSFT <[email protected]>
1 parent 6819082 commit fe2c29d

File tree

5 files changed

+232
-8
lines changed

5 files changed

+232
-8
lines changed

sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,7 +1260,7 @@ private async Task<Response<BlobDownloadStreamingResult>> DownloadStreamingInter
12601260

12611261
if (UsingClientSideEncryption)
12621262
{
1263-
options ??= new BlobDownloadOptions();
1263+
options = BlobDownloadOptions.CloneOrDefault(options) ?? new BlobDownloadOptions();
12641264
options.Range = BlobClientSideDecryptor.GetEncryptedBlobRange(options.Range);
12651265
}
12661266

@@ -1279,6 +1279,8 @@ private async Task<Response<BlobDownloadStreamingResult>> DownloadStreamingInter
12791279

12801280
ETag etag = response.Value.Details.ETag;
12811281
BlobRequestConditions conditionsWithEtag = options?.Conditions?.WithIfMatch(etag) ?? new BlobRequestConditions { IfMatch = etag };
1282+
options = BlobDownloadOptions.CloneOrDefault(options) ?? new BlobDownloadOptions();
1283+
options.Conditions = conditionsWithEtag;
12821284

12831285
// Wrap the response Content in a RetriableStream so we
12841286
// can return it before it's finished downloading, but still
@@ -1394,14 +1396,18 @@ private async Task<Response<BlobDownloadStreamingResult>> StartDownloadAsync(
13941396
CancellationToken cancellationToken = default)
13951397
{
13961398
HttpRange? pageRange = null;
1397-
if ((options?.Range ?? default) != default || startOffset != 0)
1399+
if ((options?.Range ?? default) != default)
13981400
{
13991401
pageRange = new HttpRange(
14001402
options.Range.Offset + startOffset,
14011403
options.Range.Length.HasValue ?
14021404
options.Range.Length.Value - startOffset :
14031405
(long?)null);
14041406
}
1407+
else if (startOffset != 0)
1408+
{
1409+
pageRange = new HttpRange(startOffset);
1410+
}
14051411

14061412
ClientConfiguration.Pipeline.LogTrace($"Download {Uri} with range: {pageRange}");
14071413

sdk/storage/Azure.Storage.Blobs/src/Models/BlobDownloadOptions.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
using System;
5+
using Azure.Core;
6+
47
namespace Azure.Storage.Blobs.Models
58
{
69
/// <summary>
@@ -28,5 +31,46 @@ public class BlobDownloadOptions
2831
/// REST documentation</a> for range limitation details.
2932
/// </summary>
3033
public DownloadTransactionalHashingOptions TransactionalHashingOptions { get; set; }
34+
35+
/// <summary>
36+
/// Default constructor.
37+
/// </summary>
38+
public BlobDownloadOptions()
39+
{
40+
}
41+
42+
/// <summary>
43+
/// Deep copy constructor.
44+
/// </summary>
45+
/// <param name="deepCopySource"></param>
46+
private BlobDownloadOptions(BlobDownloadOptions deepCopySource)
47+
{
48+
Argument.AssertNotNull(deepCopySource, nameof(deepCopySource));
49+
50+
Range = new HttpRange(offset: deepCopySource.Range.Offset, length: deepCopySource.Range.Length);
51+
Conditions = BlobRequestConditions.CloneOrDefault(deepCopySource.Conditions);
52+
// can't access an internal deep copy in Storage.Common
53+
TransactionalHashingOptions = deepCopySource.TransactionalHashingOptions == default
54+
? default
55+
: new DownloadTransactionalHashingOptions()
56+
{
57+
Algorithm = deepCopySource.TransactionalHashingOptions.Algorithm,
58+
Validate = deepCopySource.TransactionalHashingOptions.Validate
59+
};
60+
}
61+
62+
/// <summary>
63+
/// Creates a deep copy of the given instance, if any.
64+
/// </summary>
65+
/// <param name="deepCopySource">Instance to deep copy.</param>
66+
/// <returns>The deep copy, or null.</returns>
67+
internal static BlobDownloadOptions CloneOrDefault(BlobDownloadOptions deepCopySource)
68+
{
69+
if (deepCopySource == default)
70+
{
71+
return default;
72+
}
73+
return new BlobDownloadOptions(deepCopySource);
74+
}
3175
}
3276
}

sdk/storage/Azure.Storage.Blobs/src/Models/BlobLeaseRequestConditions.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
using Azure.Core;
5+
46
namespace Azure.Storage.Blobs.Models
57
{
68
/// <summary>
@@ -12,5 +14,43 @@ public class BlobLeaseRequestConditions : RequestConditions
1214
/// Optional SQL statement to apply to the Tags of the Blob.
1315
/// </summary>
1416
public string TagConditions { get; set; }
17+
18+
/// <summary>
19+
/// Default constructor.
20+
/// </summary>
21+
public BlobLeaseRequestConditions()
22+
{
23+
}
24+
25+
internal BlobLeaseRequestConditions(BlobLeaseRequestConditions deepCopySource)
26+
{
27+
Argument.AssertNotNull(deepCopySource, nameof(deepCopySource));
28+
29+
TagConditions = deepCopySource.TagConditions;
30+
31+
// can't get to Azure.Core internals from here; copy it's values here
32+
33+
// Azure.MatchConditions
34+
IfMatch = deepCopySource.IfMatch;
35+
IfNoneMatch = deepCopySource.IfNoneMatch;
36+
37+
// Azure.RequestConditions
38+
IfModifiedSince = deepCopySource.IfModifiedSince;
39+
IfUnmodifiedSince = deepCopySource.IfUnmodifiedSince;
40+
}
41+
42+
/// <summary>
43+
/// Creates a deep copy of the given instance, if any.
44+
/// </summary>
45+
/// <param name="deepCopySource">Instance to deep copy.</param>
46+
/// <returns>The deep copy, or null.</returns>
47+
internal static BlobLeaseRequestConditions CloneOrDefault(BlobLeaseRequestConditions deepCopySource)
48+
{
49+
if (deepCopySource == default)
50+
{
51+
return default;
52+
}
53+
return new BlobLeaseRequestConditions(deepCopySource);
54+
}
1555
}
1656
}

sdk/storage/Azure.Storage.Blobs/src/Models/BlobRequestConditions.cs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Text;
6+
using Azure.Core;
67
using Azure.Storage.Blobs.Models;
78

89
namespace Azure.Storage.Blobs.Models
@@ -18,6 +19,33 @@ public class BlobRequestConditions : BlobLeaseRequestConditions
1819
/// </summary>
1920
public string LeaseId { get; set; }
2021

22+
/// <summary>
23+
/// Default constructor.
24+
/// </summary>
25+
public BlobRequestConditions()
26+
{
27+
}
28+
29+
private BlobRequestConditions(BlobRequestConditions deepCopySource) : base(deepCopySource)
30+
{
31+
Argument.AssertNotNull(deepCopySource, nameof(deepCopySource));
32+
LeaseId = deepCopySource.LeaseId;
33+
}
34+
35+
/// <summary>
36+
/// Creates a deep copy of the given instance, if any.
37+
/// </summary>
38+
/// <param name="deepCopySource">Instance to deep copy.</param>
39+
/// <returns>The deep copy, or null.</returns>
40+
internal static BlobRequestConditions CloneOrDefault(BlobRequestConditions deepCopySource)
41+
{
42+
if (deepCopySource == default)
43+
{
44+
return default;
45+
}
46+
return new BlobRequestConditions(deepCopySource);
47+
}
48+
2149
/// <summary>
2250
/// Converts the value of the current RequestConditions object to
2351
/// its equivalent string representation.
@@ -42,14 +70,9 @@ public override string ToString()
4270
}
4371

4472
internal BlobRequestConditions WithIfMatch(ETag etag) =>
45-
new BlobRequestConditions
73+
new BlobRequestConditions(this)
4674
{
47-
LeaseId = LeaseId,
4875
IfMatch = etag,
49-
IfNoneMatch = IfNoneMatch,
50-
IfModifiedSince = IfModifiedSince,
51-
IfUnmodifiedSince = IfUnmodifiedSince,
52-
TagConditions = TagConditions,
5376
};
5477

5578
/// <summary>
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Reflection;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
using Azure.Storage.Blobs.Models;
11+
using NUnit.Framework;
12+
13+
namespace Azure.Storage.Blobs.Tests
14+
{
15+
public class ModelsTests
16+
{
17+
private bool ReflectiveValueEqual(object left, object right, int recusrsiveLimit = 10)
18+
{
19+
if (recusrsiveLimit <= 0)
20+
{
21+
throw new ArgumentException("Hit recursive limit for equality test");
22+
}
23+
24+
if (left == default && right == default)
25+
{
26+
return true;
27+
}
28+
else if (left == default || right == default)
29+
{
30+
return false;
31+
}
32+
33+
if (left.GetType() != right.GetType())
34+
{
35+
return false;
36+
}
37+
if (left.GetType().IsValueType)
38+
{
39+
return left == right;
40+
}
41+
42+
bool equal = true;
43+
PropertyInfo[] properties = left.GetType().GetProperties();
44+
foreach (PropertyInfo property in properties)
45+
{
46+
if (property.PropertyType.IsValueType)
47+
{
48+
var leftval = property.GetValue(left);
49+
var rightval = property.GetValue(right);
50+
equal &= leftval.Equals(rightval);
51+
}
52+
else
53+
{
54+
equal &= ReflectiveValueEqual(property.GetValue(left), property.GetValue(right), recusrsiveLimit - 1);
55+
}
56+
57+
if (!equal)
58+
{
59+
return false;
60+
}
61+
}
62+
return true;
63+
}
64+
65+
[Test]
66+
public void BlobDownloadOptionsDeepCopy()
67+
{
68+
// note these are all non-default values
69+
BlobDownloadOptions MakeOriginal()
70+
{
71+
return new BlobDownloadOptions
72+
{
73+
Conditions = new BlobRequestConditions
74+
{
75+
IfModifiedSince = DateTime.Now,
76+
IfUnmodifiedSince = DateTime.Now,
77+
IfMatch = new ETag("foo"),
78+
IfNoneMatch = new ETag("bar")
79+
},
80+
Range = new HttpRange(offset: 1, length: 1),
81+
TransactionalHashingOptions = new DownloadTransactionalHashingOptions
82+
{
83+
Algorithm = TransactionalHashAlgorithm.MD5,
84+
Validate = true
85+
}
86+
};
87+
}
88+
89+
var original = MakeOriginal();
90+
var deepCopy = BlobDownloadOptions.CloneOrDefault(original);
91+
92+
// test deep copy successful equality
93+
Assert.IsTrue(ReflectiveValueEqual(original, deepCopy), "deep copy did not properly clone");
94+
95+
// test deep copy no longer equal when one value changed
96+
deepCopy.Range = new HttpRange(offset: 1, length: 2);
97+
Assert.IsFalse(ReflectiveValueEqual(original, deepCopy), "change to deep copy affected original");
98+
99+
// TODO perhaps come up with exhaustive test to ensure this is true for all possible changes
100+
}
101+
102+
[Test]
103+
public void BlobDownloadOptionsDeepCopyDefault()
104+
{
105+
BlobDownloadOptions original = default;
106+
BlobDownloadOptions deepCopy = BlobDownloadOptions.CloneOrDefault(original);
107+
108+
Assert.IsNull(deepCopy);
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)