diff --git a/BreakingChanges.txt b/BreakingChanges.txt index 502e405f2..428db19a5 100644 --- a/BreakingChanges.txt +++ b/BreakingChanges.txt @@ -1,5 +1,12 @@ Tracking Breaking Changes since 4.0 +- All: Deprecated the overload for GetSharedAccessSignature that takes a SAS version because the SAS tokens generated using the current version work fine with old libraries. +- All: Updated the error message for the error that is thrown for having more than 5 shared access policy identifiers to include shares. +- All: Changed behavior to stop stripping out query parameters passed in with the resource URI. Some query parameters such as comp, restype, snapshot and api-version will still be removed. +- Blobs: MultiBufferMemoryStream objects passed as argument to upload methods are not disposed by the client library. +- Blobs: ListBlobs* can return an additional type called CloudAppendBlob in the enumeration. +- Blobs: Deprecated StartCopyFromBlob() using the Obsolete attribute. Use StartCopy() instead. +- Blobs: CreateCloudBlobClient does not throw an exception when the credentials are null to support anonymous access. - Tables: Removed Serialized attribute and ISerializable implementation from TableEntity. Implement ITabeEntity for your custom entities if you need either. Tracking Breaking Changes since 3.0 diff --git a/BuildAspNetK.cmd b/BuildAspNetK.cmd index 6bc490e1c..6c5e45807 100644 --- a/BuildAspNetK.cmd +++ b/BuildAspNetK.cmd @@ -1,8 +1,8 @@ pushd %~dp0 -call Tools\nuget.exe install dnx-coreclr-win-x86 -Version 1.0.0-beta4 -Prerelease -call Tools\nuget.exe install dnx-clr-win-x86 -Version 1.0.0-beta4 -Prerelease -call dnx-coreclr-win-x86.1.0.0-beta4\bin\dnu restore -call dnx-clr-win-x86.1.0.0-beta4\bin\dnu restore +call Tools\nuget.exe install dnx-coreclr-win-x86 -Version 1.0.0-beta5 -Prerelease +call Tools\nuget.exe install dnx-clr-win-x86 -Version 1.0.0-beta5 -Prerelease +call dnx-coreclr-win-x86.1.0.0-beta5\bin\dnu restore +call dnx-clr-win-x86.1.0.0-beta5\bin\dnu restore cd Lib\AspNet\Microsoft.WindowsAzure.Storage -call ..\..\..\dnx-coreclr-win-x86.1.0.0-beta4\bin\dnu build --configuration release +call ..\..\..\dnx-coreclr-win-x86.1.0.0-beta5\bin\dnu build --configuration release popd diff --git a/Documentation/Archive/SCL1.7 - NET API Documentation.chm b/Documentation/Archive/SCL1.7 - NET API Documentation.chm new file mode 100644 index 000000000..d3da3fc61 Binary files /dev/null and b/Documentation/Archive/SCL1.7 - NET API Documentation.chm differ diff --git a/Documentation/SCL v4.3 - WinPhone Documentation.chm b/Documentation/SCL v4.3 - WinPhone Documentation.chm new file mode 100644 index 000000000..15fccdc0c Binary files /dev/null and b/Documentation/SCL v4.3 - WinPhone Documentation.chm differ diff --git a/Documentation/SCL v4.3 - WinRT Documentation.chm b/Documentation/SCL v4.3 - WinRT Documentation.chm new file mode 100644 index 000000000..7f922977b Binary files /dev/null and b/Documentation/SCL v4.3 - WinRT Documentation.chm differ diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs index 382905bbf..a0500d921 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs @@ -33,8 +33,8 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.4.1.0")] -[assembly: AssemblyFileVersion("4.4.1.0")] -[assembly: AssemblyInformationalVersion("4.4.1.0-preview")] +[assembly: AssemblyVersion("5.0.1.0")] +[assembly: AssemblyFileVersion("5.0.1.0")] +[assembly: AssemblyInformationalVersion("5.0.1.0-preview")] [assembly: InternalsVisibleTo("Microsoft.WindowsAzure.Storage.Test")] diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/Properties/debugSettings.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage/Properties/debugSettings.json new file mode 100644 index 000000000..a44fad34a --- /dev/null +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/Properties/debugSettings.json @@ -0,0 +1,3 @@ +{ + "Profiles": [] +} \ No newline at end of file diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec index c003c2273..35ae14aac 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 4.4.1-preview + 5.0.1-preview Windows Azure Storage Microsoft Microsoft @@ -62,27 +62,26 @@ Microsoft Azure Storage team's blog - http://blogs.msdn.com/b/windowsazurestorag - - - + + + - + - - - + + - + - + - + - + diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json index c99696d10..68c2013a0 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json @@ -1,5 +1,5 @@ { - "version": "4.4.1.0", + "version": "5.0.1.0", "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for ASP.NET 5 Preview", "dependencies": { @@ -10,7 +10,6 @@ "frameworks": { "dnxcore50": { "dependencies": { - "dnx-coreclr-win-x86": "1.0.0-beta4", "System.Collections": "4.0.10-beta-*", "System.Collections.Concurrent": "4.0.0-beta-*", "System.Collections.Specialized": "4.0.0-beta-*", @@ -62,9 +61,6 @@ "System.Xml.Linq": "", "System.Runtime.Serialization.Xml": "", "System.Net.Http": "" - }, - "dependencies": { - "dnx-clr-win-x86": "1.0.0-beta4" } } }, diff --git a/Lib/ClassLibraryCommon/Blob/BlobEncryptedWriteStream.cs b/Lib/ClassLibraryCommon/Blob/BlobEncryptedWriteStream.cs index a12023259..c1bf8a3e8 100644 --- a/Lib/ClassLibraryCommon/Blob/BlobEncryptedWriteStream.cs +++ b/Lib/ClassLibraryCommon/Blob/BlobEncryptedWriteStream.cs @@ -56,9 +56,7 @@ internal BlobEncryptedWriteStream(CloudBlockBlob blockBlob, AccessCondition acce // Since this is done on the copy of the options object that the client lib maintains and not on the user's options object and is done after getting // the transform function, it should be fine. Setting this ensures that an error is not thrown when PutBlock is called internally from the write method on the stream. - // The other way to do this is to have an internal bool property on request options that specifies whether range upload operations should validate - // that encryption policy is not set. - options.EncryptionPolicy = null; + options.SkipEncryptionPolicyValidation = true; this.transform = transform; this.writeStream = new BlobWriteStream(blockBlob, accessCondition, options, operationContext) { IgnoreFlush = true }; @@ -86,15 +84,39 @@ internal BlobEncryptedWriteStream(CloudPageBlob pageBlob, long pageBlobSize, boo // Since this is done on the copy of the options object that the client lib maintains and not on the user's options object and is done after getting // the transform function, it should be fine. Setting this ensures that an error is not thrown when PutPage is called internally from the write method on the stream. - // The other way to do this is to have an internal bool property on request options that specifies whether range upload operations should validate - // that encryption policy is not set. - options.EncryptionPolicy = null; + options.SkipEncryptionPolicyValidation = true; this.transform = transform; this.writeStream = new BlobWriteStream(pageBlob, pageBlobSize, createNew, accessCondition, options, operationContext) { IgnoreFlush = true }; this.cryptoStream = new CryptoStream(this.writeStream, transform, CryptoStreamMode.Write); } + /// + /// Initializes a new instance of the BlobWriteStream class for an append blob. + /// + /// Blob reference to write to. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// The ICryptoTransform function for the request. + internal BlobEncryptedWriteStream(CloudAppendBlob appendBlob, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, ICryptoTransform transform) + { + CommonUtility.AssertNotNull("transform", transform); + + if (options.EncryptionPolicy.EncryptionMode != BlobEncryptionMode.FullBlob) + { + throw new InvalidOperationException(SR.InvalidEncryptionMode, null); + } + + // Since this is done on the copy of the options object that the client lib maintains and not on the user's options object and is done after getting + // the transform function, it should be fine. Setting this ensures that an error is not thrown when AppendBlock is called internally from the write method on the stream. + options.SkipEncryptionPolicyValidation = true; + + this.transform = transform; + this.writeStream = new BlobWriteStream(appendBlob, accessCondition, options, operationContext) { IgnoreFlush = true }; + this.cryptoStream = new CryptoStream(this.writeStream, transform, CryptoStreamMode.Write); + } + /// /// Gets a value indicating whether the current stream supports reading. /// diff --git a/Lib/ClassLibraryCommon/Blob/BlobEncryptionPolicy.cs b/Lib/ClassLibraryCommon/Blob/BlobEncryptionPolicy.cs index 38ea534ed..450ecdd70 100644 --- a/Lib/ClassLibraryCommon/Blob/BlobEncryptionPolicy.cs +++ b/Lib/ClassLibraryCommon/Blob/BlobEncryptionPolicy.cs @@ -28,7 +28,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob using System.Threading; /// - /// Represents a blob encryption policy that is used to perform envelope encryption/decryption of Azure blobs. + /// Represents an encryption policy for performing envelope encryption/decryption of Azure blobs. /// public sealed class BlobEncryptionPolicy { @@ -46,7 +46,7 @@ public sealed class BlobEncryptionPolicy /// /// Gets or sets the key resolver used to select the correct key for decrypting existing blobs. /// - /// A resolver that returns an given a keyId. + /// A resolver that returns an , given a key ID. public IKeyResolver KeyResolver { get; private set; } /// @@ -54,11 +54,11 @@ public sealed class BlobEncryptionPolicy /// /// An object of type that is used to wrap/unwrap the content key during encryption. /// The key resolver used to select the correct key for decrypting existing blobs. - /// If the generated policy is intended to be used for encryption, users are expected to provide a key at the minimum. - /// The absence of key will cause an exception to be thrown during encryption. - /// If the generated policy is intended to be used for decryption, users can provide a keyResolver. The client library will - - /// 1. Invoke the key resolver if specified to get the key. - /// 2. If resolver is not specified but a key is specified, match the key id on the key and use it. + /// If the generated policy is to be used for encryption, users are expected to provide a key at the minimum. + /// The absence of key will cause an exception to be thrown during encryption.
+ /// If the generated policy is intended to be used for decryption, users can provide a key resolver. The client library will:
+ /// 1. Invoke the key resolver, if specified, to get the key.
+ /// 2. If resolver is not specified but a key is specified, the client library will match the key ID against the key and use the key.
public BlobEncryptionPolicy(IKey key, IKeyResolver keyResolver) { this.Key = key; @@ -67,98 +67,108 @@ public BlobEncryptionPolicy(IKey key, IKeyResolver keyResolver) } /// - /// Return a reference to a given a user stream. This method is used for decrypting blobs. + /// Return a reference to a object, given a user stream. This method is used for decrypting blobs. /// /// The output stream provided by the user. - /// Reference to blob metadata object that is used to get the encryption materials. - /// The ICryptoTransform function for the request. + /// A reference to a dictionary containing blob metadata that includes the encryption data. + /// The function for the request. + /// A boolean value to indicate whether the data read from the server should be encrypted. /// The iv to use if pre-buffered. Used only for range reads. /// Value indicating if the padding mode should be set or not. /// A reference to a that will be written to. - internal Stream DecryptBlob(Stream userProvidedStream, IDictionary metadata, out ICryptoTransform transform, byte[] iv = null, bool noPadding = false) + internal Stream DecryptBlob(Stream userProvidedStream, IDictionary metadata, out ICryptoTransform transform, bool? requireEncryption, byte[] iv = null, bool noPadding = false) { CommonUtility.AssertNotNull("metadata", metadata); - string encryptionDataString; + string encryptionDataString = null; - // If encryption policy is set but the encryption metadata is absent, throw - // an exception. - if (!metadata.TryGetValue(Constants.EncryptionConstants.BlobEncryptionData, out encryptionDataString)) + // If encryption policy is set but the encryption metadata is absent, throw an exception. + bool encryptionMetadataAvailable = metadata.TryGetValue(Constants.EncryptionConstants.BlobEncryptionData, out encryptionDataString); + + if (requireEncryption.HasValue && requireEncryption.Value && !encryptionMetadataAvailable) { throw new StorageException(SR.EncryptionDataNotPresentError, null) { IsRetryable = false }; } try { - BlobEncryptionData encryptionData = JsonConvert.DeserializeObject(encryptionDataString); - - CommonUtility.AssertNotNull("ContentEncryptionIV", encryptionData.ContentEncryptionIV); - CommonUtility.AssertNotNull("EncryptedKey", encryptionData.WrappedContentKey.EncryptedKey); - - // Throw if the encryption protocol on the blob doesn't match the version that this client library understands - // and is able to decrypt. - if (encryptionData.EncryptionAgent.Protocol != Constants.EncryptionConstants.EncryptionProtocolV1) + if (encryptionDataString != null) { - throw new StorageException(SR.EncryptionProtocolVersionInvalid, null) { IsRetryable = false }; - } + BlobEncryptionData encryptionData = JsonConvert.DeserializeObject(encryptionDataString); - // Throw if neither the key nor the resolver are set. - if (this.Key == null && this.KeyResolver == null) - { - throw new StorageException(SR.KeyAndResolverMissingError, null) { IsRetryable = false }; - } + CommonUtility.AssertNotNull("ContentEncryptionIV", encryptionData.ContentEncryptionIV); + CommonUtility.AssertNotNull("EncryptedKey", encryptionData.WrappedContentKey.EncryptedKey); - byte[] contentEncryptionKey = null; + // Throw if the encryption protocol on the blob doesn't match the version that this client library understands + // and is able to decrypt. + if (encryptionData.EncryptionAgent.Protocol != Constants.EncryptionConstants.EncryptionProtocolV1) + { + throw new StorageException(SR.EncryptionProtocolVersionInvalid, null) { IsRetryable = false }; + } - // 1. Invoke the key resolver if specified to get the key. If the resolver is specified but does not have a - // mapping for the key id, an error should be thrown. This is important for key rotation scenario. - // 2. If resolver is not specified but a key is specified, match the key id on the key and and use it. - // Calling UnwrapKeyAsync synchronously is fine because for the storage client scenario, unwrap happens - // locally. No service call is made. - if (this.KeyResolver != null) - { - IKey keyEncryptionKey = this.KeyResolver.ResolveKeyAsync(encryptionData.WrappedContentKey.KeyId, CancellationToken.None).Result; + // Throw if neither the key nor the resolver are set. + if (this.Key == null && this.KeyResolver == null) + { + throw new StorageException(SR.KeyAndResolverMissingError, null) { IsRetryable = false }; + } - CommonUtility.AssertNotNull("KeyEncryptionKey", keyEncryptionKey); - contentEncryptionKey = keyEncryptionKey.UnwrapKeyAsync(encryptionData.WrappedContentKey.EncryptedKey, encryptionData.WrappedContentKey.Algorithm, CancellationToken.None).Result; - } - else - { - if (this.Key.Kid == encryptionData.WrappedContentKey.KeyId) + byte[] contentEncryptionKey = null; + + // 1. Invoke the key resolver if specified to get the key. If the resolver is specified but does not have a + // mapping for the key id, an error should be thrown. This is important for key rotation scenario. + // 2. If resolver is not specified but a key is specified, match the key id on the key and and use it. + // Calling UnwrapKeyAsync synchronously is fine because for the storage client scenario, unwrap happens + // locally. No service call is made. + if (this.KeyResolver != null) { - contentEncryptionKey = this.Key.UnwrapKeyAsync(encryptionData.WrappedContentKey.EncryptedKey, encryptionData.WrappedContentKey.Algorithm, CancellationToken.None).Result; + IKey keyEncryptionKey = this.KeyResolver.ResolveKeyAsync(encryptionData.WrappedContentKey.KeyId, CancellationToken.None).Result; + + CommonUtility.AssertNotNull("KeyEncryptionKey", keyEncryptionKey); + contentEncryptionKey = keyEncryptionKey.UnwrapKeyAsync(encryptionData.WrappedContentKey.EncryptedKey, encryptionData.WrappedContentKey.Algorithm, CancellationToken.None).Result; } else { - throw new StorageException(SR.KeyMismatch, null) { IsRetryable = false }; + if (this.Key.Kid == encryptionData.WrappedContentKey.KeyId) + { + contentEncryptionKey = this.Key.UnwrapKeyAsync(encryptionData.WrappedContentKey.EncryptedKey, encryptionData.WrappedContentKey.Algorithm, CancellationToken.None).Result; + } + else + { + throw new StorageException(SR.KeyMismatch, null) { IsRetryable = false }; + } } - } - switch (encryptionData.EncryptionAgent.EncryptionAlgorithm) - { - case EncryptionAlgorithm.AES_CBC_256: + switch (encryptionData.EncryptionAgent.EncryptionAlgorithm) + { + case EncryptionAlgorithm.AES_CBC_256: #if WINDOWS_DESKTOP && !WINDOWS_PHONE - using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider()) + using (AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider()) #else using (AesManaged aesProvider = new AesManaged()) #endif - { - aesProvider.IV = iv != null ? iv : encryptionData.ContentEncryptionIV; - aesProvider.Key = contentEncryptionKey; - - if (noPadding) { + aesProvider.IV = iv != null ? iv : encryptionData.ContentEncryptionIV; + aesProvider.Key = contentEncryptionKey; + + if (noPadding) + { #if WINDOWS_DESKTOP && !WINDOWS_PHONE - aesProvider.Padding = PaddingMode.None; + aesProvider.Padding = PaddingMode.None; #endif - } + } - transform = aesProvider.CreateDecryptor(); - return new CryptoStream(userProvidedStream, transform, CryptoStreamMode.Write); - } + transform = aesProvider.CreateDecryptor(); + return new CryptoStream(userProvidedStream, transform, CryptoStreamMode.Write); + } - default: - throw new StorageException(SR.InvalidEncryptionAlgorithm, null) { IsRetryable = false }; + default: + throw new StorageException(SR.InvalidEncryptionAlgorithm, null) { IsRetryable = false }; + } + } + else + { + transform = null; + return userProvidedStream; } } catch (JsonException ex) @@ -185,7 +195,7 @@ internal static Stream WrapUserStreamWithDecryptStream(CloudBlob blob, Stream us // The user provided stream should be wrapped in a TruncatingNonCloseableStream in order to // avoid closing the user stream when the crypto stream is closed to flush the final decrypted // block of data. - Stream decryptStream = options.EncryptionPolicy.DecryptBlob(new TruncatingNonCloseableStream(userProvidedStream), attributes.Metadata, out transform, null, blob.BlobType == BlobType.PageBlob); + Stream decryptStream = options.EncryptionPolicy.DecryptBlob(new NonCloseableStream(userProvidedStream), attributes.Metadata, out transform, options.RequireEncryption, null, blob.BlobType == BlobType.PageBlob); return decryptStream; } else @@ -193,7 +203,7 @@ internal static Stream WrapUserStreamWithDecryptStream(CloudBlob blob, Stream us // Check if end offset lies in the last AES block and send this information over to set the correct padding mode. bool noPadding = blob.BlobType == BlobType.PageBlob || (endOffset.HasValue && endOffset.Value < attributes.Properties.Length - 16); transform = null; - return new BlobDecryptStream(userProvidedStream, attributes.Metadata, userSpecifiedLength, discardFirst, bufferIV, noPadding, options.EncryptionPolicy); + return new BlobDecryptStream(userProvidedStream, attributes.Metadata, userSpecifiedLength, discardFirst, bufferIV, noPadding, options.EncryptionPolicy, options.RequireEncryption); } } @@ -232,7 +242,7 @@ internal ICryptoTransform CreateAndSetEncryptionContext(IDictionary(); encryptionData.ContentEncryptionIV = aesProvider.IV; - metadata.Add(Constants.EncryptionConstants.BlobEncryptionData, JsonConvert.SerializeObject(encryptionData)); + metadata[Constants.EncryptionConstants.BlobEncryptionData] = JsonConvert.SerializeObject(encryptionData); return aesProvider.CreateEncryptor(); } } diff --git a/Lib/ClassLibraryCommon/Blob/BlobWriteStream.cs b/Lib/ClassLibraryCommon/Blob/BlobWriteStream.cs index ae17c111a..df1ae9585 100644 --- a/Lib/ClassLibraryCommon/Blob/BlobWriteStream.cs +++ b/Lib/ClassLibraryCommon/Blob/BlobWriteStream.cs @@ -17,12 +17,14 @@ namespace Microsoft.WindowsAzure.Storage.Blob { + using Microsoft.WindowsAzure.Storage.Blob.Protocol; using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Diagnostics.CodeAnalysis; using System.IO; + using System.Net; using System.Threading; #if WINDOWS_PHONE @@ -69,6 +71,18 @@ internal BlobWriteStream(CloudPageBlob pageBlob, long pageBlobSize, bool createN { } + /// + /// Initializes a new instance of the BlobWriteStream class for an append blob. + /// + /// Blob reference to write to. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + internal BlobWriteStream(CloudAppendBlob appendBlob, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + : base(appendBlob, accessCondition, options, operationContext) + { + } + /// /// Sets the position within the current stream. /// @@ -93,7 +107,7 @@ public override long Seek(long offset, SeekOrigin origin) } this.currentOffset = newOffset; - this.currentPageOffset = newOffset; + this.currentBlobOffset = newOffset; return this.currentOffset; } @@ -367,6 +381,8 @@ public override void Commit() { if (this.blockBlob != null) { + // This block of code is for block blobs. PutBlockList needs to be called with the list of block IDs uploaded in order + // to commit the blocks. if (this.blobMD5 != null) { this.blockBlob.Properties.ContentMD5 = this.blobMD5.ComputeHash(); @@ -380,11 +396,13 @@ public override void Commit() } else { + // For Page blobs and append blobs, only if StoreBlobContentMD5 is set to true, the stream would have caclculated an MD5 + // which should be uploaded to the server using SetProperties. if (this.blobMD5 != null) { - this.pageBlob.Properties.ContentMD5 = this.blobMD5.ComputeHash(); + this.Blob.Properties.ContentMD5 = this.blobMD5.ComputeHash(); - this.pageBlob.SetProperties( + this.Blob.SetProperties( this.accessCondition, this.options, this.operationContext); @@ -463,9 +481,9 @@ private void CommitFlushCallback(IAsyncResult ar) { if (this.blobMD5 != null) { - this.pageBlob.Properties.ContentMD5 = this.blobMD5.ComputeHash(); + this.Blob.Properties.ContentMD5 = this.blobMD5.ComputeHash(); - ICancellableAsyncResult result = this.pageBlob.BeginSetProperties( + ICancellableAsyncResult result = this.Blob.BeginSetProperties( this.accessCondition, this.options, this.operationContext, @@ -516,7 +534,7 @@ private void PutBlockListCallback(IAsyncResult ar) } /// - /// Called when the page blob commit operation completes. + /// Called when the page or append blob commit operation completes. /// /// The result of the asynchronous operation. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Needed to ensure exceptions are not thrown on threadpool threads.")] @@ -527,7 +545,7 @@ private void SetPropertiesCallback(IAsyncResult ar) try { - this.pageBlob.EndSetProperties(ar); + this.Blob.EndSetProperties(ar); storageAsyncResult.OnComplete(); } catch (Exception e) @@ -571,7 +589,7 @@ private void DispatchWrite(StorageAsyncResult asyncResult) this.blockList.Add(blockId); this.WriteBlock(bufferToUpload, blockId, bufferMD5, asyncResult); } - else + else if (this.pageBlob != null) { if ((bufferToUpload.Length % Constants.PageSize) != 0) { @@ -579,10 +597,25 @@ private void DispatchWrite(StorageAsyncResult asyncResult) throw this.lastException; } - long offset = this.currentPageOffset; - this.currentPageOffset += bufferToUpload.Length; + long offset = this.currentBlobOffset; + this.currentBlobOffset += bufferToUpload.Length; this.WritePages(bufferToUpload, offset, bufferMD5, asyncResult); } + else + { + long offset = this.currentBlobOffset; + this.currentBlobOffset += bufferToUpload.Length; + + // We cannot differentiate between max size condition failing only in the retry versus failing in the first attempt and retry. + // So we will eliminate the latter and handle the former in the append operation callback. + if (this.accessCondition.IfMaxSizeLessThanOrEqual.HasValue && this.currentBlobOffset > this.accessCondition.IfMaxSizeLessThanOrEqual.Value) + { + this.lastException = new IOException(SR.InvalidBlockSize); + throw this.lastException; + } + + this.WriteAppendBlock(bufferToUpload, offset, bufferMD5, asyncResult); + } } /// @@ -621,6 +654,8 @@ private void WriteBlock(Stream blockData, string blockId, string blockMD5, Stora catch (Exception e) { this.lastException = e; + this.noPendingWritesEvent.Decrement(); + this.parallelOperationSemaphore.Release(); } finally { @@ -693,6 +728,8 @@ private void WritePages(Stream pageData, long offset, string contentMD5, Storage catch (Exception e) { this.lastException = e; + this.noPendingWritesEvent.Decrement(); + this.parallelOperationSemaphore.Release(); } finally { @@ -728,5 +765,105 @@ private void WritePagesCallback(IAsyncResult ar) this.noPendingWritesEvent.Decrement(); this.parallelOperationSemaphore.Release(); } + + /// + /// Starts an asynchronous AppendBlock operation as soon as the parallel + /// operation semaphore becomes available. Since parallelism is always set + /// to 1 for append blobs, appendblock operations are called serially. + /// + /// Data to be uploaded. + /// Offset within the append blob to be used to set the append offset conditional header. + /// MD5 hash of the data to be uploaded. + /// The reference to the pending asynchronous request to finish. + private void WriteAppendBlock(Stream blockData, long offset, string blockMD5, StorageAsyncResult asyncResult) + { + this.noPendingWritesEvent.Increment(); + this.parallelOperationSemaphore.WaitAsync(calledSynchronously => + { + try + { + this.accessCondition.IfAppendPositionEqual = offset; + + int previousResultsCount = this.operationContext.RequestResults.Count; + ICancellableAsyncResult result = this.appendBlob.BeginAppendBlock( + blockData, + blockMD5, + this.accessCondition, + this.options, + this.operationContext, + this.AppendBlockCallback, + previousResultsCount /* state */); + + if (asyncResult != null) + { + // We do not need to do this inside a lock, as asyncResult is + // not returned to the user yet. + asyncResult.CancelDelegate = result.Cancel; + } + } + catch (Exception e) + { + this.lastException = e; + this.noPendingWritesEvent.Decrement(); + this.parallelOperationSemaphore.Release(); + } + finally + { + if (asyncResult != null) + { + asyncResult.UpdateCompletedSynchronously(calledSynchronously); + asyncResult.OnComplete(this.lastException); + } + } + }); + } + + /// + /// Called when the asynchronous AppendBlock operation completes. + /// + /// The result of the asynchronous operation. + private void AppendBlockCallback(IAsyncResult ar) + { + try + { + this.appendBlob.EndAppendBlock(ar); + } + catch (StorageException e) + { + if (this.options.AbsorbConditionalErrorsOnRetry.Value + && e.RequestInformation.HttpStatusCode == (int)HttpStatusCode.PreconditionFailed) + { + int previousResultsCount = (int)ar.AsyncState; + StorageExtendedErrorInformation extendedInfo = e.RequestInformation.ExtendedErrorInformation; + if (extendedInfo != null + && (extendedInfo.ErrorCode == BlobErrorCodeStrings.InvalidAppendCondition || extendedInfo.ErrorCode == BlobErrorCodeStrings.InvalidMaxBlobSizeCondition) + && (this.operationContext.RequestResults.Count - previousResultsCount > 1)) + { + // Pre-condition failure on a retry should be ignored in a single writer scenario since the request + // succeeded in the first attempt. + Logger.LogWarning(this.operationContext, SR.PreconditionFailureIgnored); + } + else + { + this.lastException = e; + } + } + else + { + this.lastException = e; + } + } + catch (Exception e) + { + this.lastException = e; + } + + // This must be called in a separate thread than the user's + // callback to prevent a deadlock in case the callback is blocking. + // If they are called in the same thread, this call must take + // place before the user's callback. + this.noPendingWritesEvent.Decrement(); + this.parallelOperationSemaphore.Release(); + } } } diff --git a/Lib/ClassLibraryCommon/Blob/CloudAppendBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudAppendBlob.cs new file mode 100644 index 000000000..9518108b8 --- /dev/null +++ b/Lib/ClassLibraryCommon/Blob/CloudAppendBlob.cs @@ -0,0 +1,2915 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.Blob +{ + using Microsoft.WindowsAzure.Storage.Blob.Protocol; + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Core.Executor; + using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Net; + using System.Security.Cryptography; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Represents an append blob, a type of blob where blocks of data are always committed to the end of the blob. + /// + public sealed partial class CloudAppendBlob : CloudBlob, ICloudBlob + { +#if SYNC + /// + /// Opens a stream for writing to the blob. + /// + /// Use true to create a new append blob or overwrite an existing one, false to append to an existing blob. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// A object. + /// + /// Note that this method always makes a call to the method under the covers. + /// Set the property before calling this method to specify the block size to write, in bytes, + /// ranging from between 16 KB and 4 MB inclusive. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public CloudBlobStream OpenWrite(bool createNew, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + this.attributes.AssertNoSnapshot(); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, this.BlobType, this.ServiceClient, false); + +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + ICryptoTransform transform = null; +#endif + + if (createNew) + { +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + if (options != null && options.EncryptionPolicy != null) + { + transform = options.EncryptionPolicy.CreateAndSetEncryptionContext(this.Metadata, false /* noPadding */); + } +#endif + this.CreateOrReplace(accessCondition, options, operationContext); + } + else + { + if (modifiedOptions.StoreBlobContentMD5.Value) + { + throw new ArgumentException(SR.MD5NotPossible); + } + +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + if (modifiedOptions.EncryptionPolicy != null) + { + throw new ArgumentException(SR.EncryptionNotSupportedForExistingBlobs); + } +#endif + // Although we don't need any properties from the service, we should make this call in order to honor the user specified conditional headers + // while opening an existing stream and to get the append position for an existing blob if user didn't specify one. + this.FetchAttributes(accessCondition, options, operationContext); + } + + if (accessCondition != null) + { + accessCondition = new AccessCondition() { LeaseId = accessCondition.LeaseId, IfAppendPositionEqual = accessCondition.IfAppendPositionEqual, IfMaxSizeLessThanOrEqual = accessCondition.IfMaxSizeLessThanOrEqual }; + } + +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + if (modifiedOptions.EncryptionPolicy != null) + { + return new BlobEncryptedWriteStream(this, accessCondition, modifiedOptions, operationContext, transform); + } + else +#endif + { + return new BlobWriteStream(this, accessCondition, modifiedOptions, operationContext); + } + } +#endif + + /// + /// Begins an asynchronous operation to open a stream for writing to the blob. + /// + /// Use true to create a new append blob or overwrite an existing one, false to append to an existing blob. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Note that this method always makes a call to the method under the covers. + /// Set the property before calling this method to specify the block size to write, in bytes, + /// ranging from between 16 KB and 4 MB inclusive. + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginOpenWrite(bool createNew, AsyncCallback callback, object state) + { + return this.BeginOpenWrite(createNew, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to open a stream for writing to the blob. + /// + /// Use true to create a new append blob or overwrite an existing one, false to append to an existing blob. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Note that this method always makes a call to the method under the covers. + /// Set the property before calling this method to specify the block size to write, in bytes, + /// ranging from between 16 KB and 4 MB inclusive. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Needed to ensure exceptions are not thrown on threadpool threads.")] + [DoesServiceRequest] + public ICancellableAsyncResult BeginOpenWrite(bool createNew, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + this.attributes.AssertNoSnapshot(); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, this.BlobType, this.ServiceClient, false); + + StorageAsyncResult storageAsyncResult = new StorageAsyncResult(callback, state); + ICancellableAsyncResult result; + +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + ICryptoTransform transform = null; +#endif + + if (createNew) + { +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + if (options != null && options.EncryptionPolicy != null) + { + transform = options.EncryptionPolicy.CreateAndSetEncryptionContext(this.Metadata, false /* noPadding */); + } +#endif + result = this.BeginCreateOrReplace( + accessCondition, + options, + operationContext, + ar => + { + storageAsyncResult.UpdateCompletedSynchronously(ar.CompletedSynchronously); + + try + { + this.EndCreateOrReplace(ar); + + if (accessCondition != null) + { + accessCondition = new AccessCondition() { LeaseId = accessCondition.LeaseId, IfAppendPositionEqual = accessCondition.IfAppendPositionEqual, IfMaxSizeLessThanOrEqual = accessCondition.IfMaxSizeLessThanOrEqual }; + } + +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + if (modifiedOptions.EncryptionPolicy != null) + { + storageAsyncResult.Result = new BlobEncryptedWriteStream(this, accessCondition, modifiedOptions, operationContext, transform); + } + else +#endif + { + storageAsyncResult.Result = new BlobWriteStream(this, accessCondition, modifiedOptions, operationContext); + } + + storageAsyncResult.OnComplete(); + } + catch (Exception e) + { + storageAsyncResult.OnComplete(e); + } + }, + null /* state */); + } + else + { + if (modifiedOptions.StoreBlobContentMD5.Value) + { + throw new ArgumentException(SR.MD5NotPossible); + } + +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + if (modifiedOptions.EncryptionPolicy != null) + { + throw new ArgumentException(SR.EncryptionNotSupportedForExistingBlobs); + } +#endif + // Although we don't need any properties from the service, we should make this call in order to honor the user specified conditional headers + // while opening an existing stream and to get the append position for an existing blob if user didn't specify one. + result = this.BeginFetchAttributes( + accessCondition, + options, + operationContext, + ar => + { + storageAsyncResult.UpdateCompletedSynchronously(ar.CompletedSynchronously); + + try + { + this.EndFetchAttributes(ar); + + if (accessCondition != null) + { + accessCondition = new AccessCondition() { LeaseId = accessCondition.LeaseId, IfAppendPositionEqual = accessCondition.IfAppendPositionEqual }; + } + + storageAsyncResult.Result = new BlobWriteStream(this, accessCondition, modifiedOptions, operationContext); + storageAsyncResult.OnComplete(); + } + catch (Exception e) + { + storageAsyncResult.OnComplete(e); + } + }, + null /* state */); + } + + storageAsyncResult.CancelDelegate = result.Cancel; + return storageAsyncResult; + } + + /// + /// Ends an asynchronous operation to open a stream for writing to the blob. + /// + /// An that references the pending asynchronous operation. + /// A object. + public CloudBlobStream EndOpenWrite(IAsyncResult asyncResult) + { + StorageAsyncResult storageAsyncResult = (StorageAsyncResult)asyncResult; + storageAsyncResult.End(); + return storageAsyncResult.Result; + } + +#if TASK + /// + /// Initiates an asynchronous operation to open a stream for writing to the blob. + /// + /// Use true to create a new append blob or overwrite an existing one, false to append to an existing blob. + /// A object of type that represents the asynchronous operation. + /// + /// Note that this method always makes a call to the method under the covers. + /// Set the property before calling this method to specify the block size to write, in bytes, + /// ranging from between 16 KB and 4 MB inclusive. + /// + [DoesServiceRequest] + public Task OpenWriteAsync(bool createNew) + { + return this.OpenWriteAsync(createNew, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to open a stream for writing to the blob. + /// + /// Use true to create a new append blob or overwrite an existing one, false to append to an existing blob. + /// A to observe while waiting for a task to complete. + /// A object of type that represents the asynchronous operation. + /// + /// Note that this method always makes a call to the method under the covers. + /// Set the property before calling this method to specify the block size to write, in bytes, + /// ranging from between 16 KB and 4 MB inclusive. + /// + [DoesServiceRequest] + public Task OpenWriteAsync(bool createNew, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginOpenWrite, this.EndOpenWrite, createNew, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to open a stream for writing to the blob. + /// + /// Use true to create a new append blob or overwrite an existing one, false to append to an existing blob. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object of type that represents the asynchronous operation. + /// + /// Note that this method always makes a call to the method under the covers. + /// Set the property before calling this method to specify the block size to write, in bytes, + /// ranging from between 16 KB and 4 MB inclusive. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task OpenWriteAsync(bool createNew, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.OpenWriteAsync(createNew, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to open a stream for writing to the blob. + /// + /// Use true to create a new append blob or overwrite an existing one, false to append to an existing blob. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object of type that represents the asynchronous operation. + /// + /// Note that this method always makes a call to the method under the covers. + /// Set the property before calling this method to specify the block size to write, in bytes, + /// ranging from between 16 KB and 4 MB inclusive. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task OpenWriteAsync(bool createNew, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginOpenWrite, this.EndOpenWrite, createNew, accessCondition, options, operationContext, cancellationToken); + } +#endif + +#if SYNC + /// + /// Uploads a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public void UploadFromStream(Stream source, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + this.UploadFromStreamHelper(source, null /* length */, true /* createNew */, accessCondition, options, operationContext); + } + + /// + /// Uploads a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public void UploadFromStream(Stream source, long length, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + this.UploadFromStreamHelper(source, length, true /* createNew */, accessCondition, options, operationContext); + } + + /// + /// Appends a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public void AppendFromStream(Stream source, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + this.UploadFromStreamHelper(source, null /* length */, false /* createNew */, accessCondition, options, operationContext); + } + + /// + /// Appends a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public void AppendFromStream(Stream source, long length, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + this.UploadFromStreamHelper(source, length, false /* createNew */, accessCondition, options, operationContext); + } + + /// + /// Uploads a stream to an append blob. If the blob already exists, it will be overwritten. + /// + /// A object providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// true if the append blob is newly created, false otherwise. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + internal void UploadFromStreamHelper(Stream source, long? length, bool createNew, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + CommonUtility.AssertNotNull("source", source); + + if (length.HasValue) + { + CommonUtility.AssertInBounds("length", length.Value, 1); + + if (source.CanSeek && length > source.Length - source.Position) + { + throw new ArgumentOutOfRangeException("length", SR.StreamLengthShortError); + } + } + + this.attributes.AssertNoSnapshot(); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); + operationContext = operationContext ?? new OperationContext(); + + using (CloudBlobStream blobStream = this.OpenWrite(createNew, accessCondition, modifiedOptions, operationContext)) + { + using (ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions)) + { + source.WriteToSync(blobStream, length, null /* maxLength */, false, true, tempExecutionState, null /* streamCopyState */); + blobStream.Commit(); + } + } + } +#endif + + /// + /// Begins an asynchronous operation to upload a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginUploadFromStream(Stream source, AsyncCallback callback, object state) + { + return this.BeginUploadFromStreamHelper(source, null /* length */, true /* createNew */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to upload a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginUploadFromStream(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginUploadFromStreamHelper(source, null /* length */, true /* createNew */, accessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to upload a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// Specifies the number of bytes from the Stream source to upload from the start position. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginUploadFromStream(Stream source, long length, AsyncCallback callback, object state) + { + return this.BeginUploadFromStreamHelper(source, length, true /* createNew */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to upload a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// Specifies the number of bytes from the Stream source to upload from the start position. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginUploadFromStream(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginUploadFromStreamHelper(source, length, true /* createNew */, accessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to append a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginAppendFromStream(Stream source, AsyncCallback callback, object state) + { + return this.BeginUploadFromStreamHelper(source, null /* length */, false /* createNew */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to append a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginAppendFromStream(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginUploadFromStreamHelper(source, null /* length */, false /* createNew */, accessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to append a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// Specifies the number of bytes from the Stream source to upload from the start position. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginAppendFromStream(Stream source, long length, AsyncCallback callback, object state) + { + return this.BeginUploadFromStreamHelper(source, length, false /* createNew */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to append a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// Specifies the number of bytes from the Stream source to upload from the start position. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginAppendFromStream(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginUploadFromStreamHelper(source, length, false /* createNew */, accessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to upload a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// Specifies the number of bytes from the Stream source to upload from the start position. + /// true if the append blob is newly created, false otherwise. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Needed to ensure exceptions are not thrown on threadpool threads.")] + [DoesServiceRequest] + internal ICancellableAsyncResult BeginUploadFromStreamHelper(Stream source, long? length, bool createNew, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + CommonUtility.AssertNotNull("source", source); + + if (length.HasValue) + { + CommonUtility.AssertInBounds("length", length.Value, 1); + + if (source.CanSeek && length > source.Length - source.Position) + { + throw new ArgumentOutOfRangeException("length", SR.StreamLengthShortError); + } + } + + this.attributes.AssertNoSnapshot(); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); + + ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions); + StorageAsyncResult storageAsyncResult = new StorageAsyncResult(callback, state); + + ICancellableAsyncResult result = this.BeginOpenWrite( + createNew, + accessCondition, + modifiedOptions, + operationContext, + ar => + { + storageAsyncResult.UpdateCompletedSynchronously(ar.CompletedSynchronously); + + lock (storageAsyncResult.CancellationLockerObject) + { + storageAsyncResult.CancelDelegate = null; + try + { + CloudBlobStream blobStream = this.EndOpenWrite(ar); + storageAsyncResult.OperationState = blobStream; + + source.WriteToAsync( + blobStream, + length, + null /* maxLength */, + false, + tempExecutionState, + null /* streamCopyState */, + completedState => + { + storageAsyncResult.UpdateCompletedSynchronously(completedState.CompletedSynchronously); + if (completedState.ExceptionRef != null) + { + storageAsyncResult.OnComplete(completedState.ExceptionRef); + } + else + { + try + { + lock (storageAsyncResult.CancellationLockerObject) + { + storageAsyncResult.CancelDelegate = null; + ICancellableAsyncResult commitResult = blobStream.BeginCommit( + CloudBlob.BlobOutputStreamCommitCallback, + storageAsyncResult); + + storageAsyncResult.CancelDelegate = commitResult.Cancel; + if (storageAsyncResult.CancelRequested) + { + storageAsyncResult.Cancel(); + } + } + } + catch (Exception e) + { + storageAsyncResult.OnComplete(e); + } + } + }); + + storageAsyncResult.CancelDelegate = tempExecutionState.Cancel; + if (storageAsyncResult.CancelRequested) + { + storageAsyncResult.Cancel(); + } + } + catch (Exception e) + { + storageAsyncResult.OnComplete(e); + } + } + }, + null /* state */); + + // We do not need to do this inside a lock, as storageAsyncResult is + // not returned to the user yet. + storageAsyncResult.CancelDelegate = result.Cancel; + + return storageAsyncResult; + } + + /// + /// Ends an asynchronous operation to upload a stream to an append blob. + /// + /// An that references the pending asynchronous operation. + public void EndUploadFromStream(IAsyncResult asyncResult) + { + StorageAsyncResult storageAsyncResult = (StorageAsyncResult)asyncResult; + storageAsyncResult.End(); + } + + /// + /// Ends an asynchronous operation to append a stream to an append blob. + /// + /// An that references the pending asynchronous operation. + public void EndAppendFromStream(IAsyncResult asyncResult) + { + StorageAsyncResult storageAsyncResult = (StorageAsyncResult)asyncResult; + storageAsyncResult.End(); + } + +#if TASK + /// + /// Initiates an asynchronous operation to upload a stream to an append blob. If the blob already exists, it will be overwritten. + /// + /// A object providing the blob content. + /// A object that represents the asynchronous operation. + /// + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromStreamAsync(Stream source) + { + return this.UploadFromStreamAsync(source, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to upload a stream to an append blob. If the blob already exists, it will be overwritten. + /// + /// A object providing the blob content. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromStreamAsync(Stream source, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromStream, this.EndUploadFromStream, source, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to upload a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.UploadFromStreamAsync(source, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to upload a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromStream, this.EndUploadFromStream, source, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to upload a stream to an append blob. If the blob already exists, it will be overwritten. + /// + /// A object providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// A object that represents the asynchronous operation. + /// + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromStreamAsync(Stream source, long length) + { + return this.UploadFromStreamAsync(source, length, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to upload a stream to an append blob. If the blob already exists, it will be overwritten. + /// + /// A object providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromStreamAsync(Stream source, long length, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromStream, this.EndUploadFromStream, source, length, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to upload a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.UploadFromStreamAsync(source, length, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to upload a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromStream, this.EndUploadFromStream, source, length, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to append a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + [DoesServiceRequest] + public Task AppendFromStreamAsync(Stream source) + { + return this.AppendFromStreamAsync(source, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to append a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + [DoesServiceRequest] + public Task AppendFromStreamAsync(Stream source, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginAppendFromStream, this.EndAppendFromStream, source, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to append a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.AppendFromStreamAsync(source, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to append a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginAppendFromStream, this.EndAppendFromStream, source, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to append a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + [DoesServiceRequest] + public Task AppendFromStreamAsync(Stream source, long length) + { + return this.AppendFromStreamAsync(source, length, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to append a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + [DoesServiceRequest] + public Task AppendFromStreamAsync(Stream source, long length, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginAppendFromStream, this.EndAppendFromStream, source, length, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to append a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.AppendFromStreamAsync(source, length, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to append a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// A object providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginAppendFromStream, this.EndAppendFromStream, source, length, accessCondition, options, operationContext, cancellationToken); + } +#endif + +#if SYNC + /// + /// Uploads a file to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public void UploadFromFile(string path, FileMode mode, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + CommonUtility.AssertNotNull("path", path); + + using (FileStream fileStream = new FileStream(path, mode, FileAccess.Read)) + { + this.UploadFromStream(fileStream, accessCondition, options, operationContext); + } + } + + /// + /// Appends a file to an append blob. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public void AppendFromFile(string path, FileMode mode, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + CommonUtility.AssertNotNull("path", path); + + using (FileStream fileStream = new FileStream(path, mode, FileAccess.Read)) + { + this.AppendFromStream(fileStream, accessCondition, options, operationContext); + } + } +#endif + + /// + /// Begins an asynchronous operation to upload a file to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginUploadFromFile(string path, FileMode mode, AsyncCallback callback, object state) + { + return this.BeginUploadFromFile(path, mode, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to upload a file to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginUploadFromFile(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + CommonUtility.AssertNotNull("path", path); + + FileStream fileStream = new FileStream(path, mode, FileAccess.Read); + StorageAsyncResult storageAsyncResult = new StorageAsyncResult(callback, state) + { + OperationState = fileStream + }; + + try + { + ICancellableAsyncResult asyncResult = this.BeginUploadFromStream(fileStream, accessCondition, options, operationContext, this.UploadFromFileCallback, storageAsyncResult); + storageAsyncResult.CancelDelegate = asyncResult.Cancel; + return storageAsyncResult; + } + catch (Exception) + { + fileStream.Dispose(); + throw; + } + } + + /// + /// Begins an asynchronous operation to append a file to an append blob. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginAppendFromFile(string path, FileMode mode, AsyncCallback callback, object state) + { + return this.BeginAppendFromFile(path, mode, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to append a file to an append blob. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginAppendFromFile(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + CommonUtility.AssertNotNull("path", path); + + FileStream fileStream = new FileStream(path, mode, FileAccess.Read); + StorageAsyncResult storageAsyncResult = new StorageAsyncResult(callback, state) + { + OperationState = fileStream + }; + + try + { + ICancellableAsyncResult asyncResult = this.BeginAppendFromStream(fileStream, accessCondition, options, operationContext, this.UploadFromFileCallback, storageAsyncResult); + storageAsyncResult.CancelDelegate = asyncResult.Cancel; + return storageAsyncResult; + } + catch (Exception) + { + fileStream.Dispose(); + throw; + } + } + + /// + /// Called when the asynchronous UploadFromStream operation completes. + /// + /// The result of the asynchronous operation. + private void UploadFromFileCallback(IAsyncResult asyncResult) + { + StorageAsyncResult storageAsyncResult = (StorageAsyncResult)asyncResult.AsyncState; + Exception exception = null; + + try + { + this.EndUploadFromStream(asyncResult); + } + catch (Exception e) + { + exception = e; + } + + // We should do FileStream disposal in a separate try-catch block + // because we want to close the file even if the operation fails. + try + { + FileStream fileStream = (FileStream)storageAsyncResult.OperationState; + fileStream.Dispose(); + } + catch (Exception e) + { + exception = e; + } + + storageAsyncResult.OnComplete(exception); + } + + /// + /// Ends an asynchronous operation to upload a file to an append blob. Recommended only for single-writer scenarios. + /// + /// An that references the pending asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + public void EndUploadFromFile(IAsyncResult asyncResult) + { + StorageAsyncResult res = (StorageAsyncResult)asyncResult; + res.End(); + } + + /// + /// Ends an asynchronous operation to upload a file to an append blob. Recommended only for single-writer scenarios. + /// + /// An that references the pending asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + public void EndAppendFromFile(IAsyncResult asyncResult) + { + StorageAsyncResult res = (StorageAsyncResult)asyncResult; + res.End(); + } + +#if TASK + /// + /// Initiates an asynchronous operation to upload a file to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromFileAsync(string path, FileMode mode) + { + return this.UploadFromFileAsync(path, mode, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to upload a file to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromFileAsync(string path, FileMode mode, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromFile, this.EndUploadFromFile, path, mode, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to upload a file to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.UploadFromFileAsync(path, mode, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to upload a file to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromFile, this.EndUploadFromFile, path, mode, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to append a file to an append blob. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + [DoesServiceRequest] + public Task AppendFromFileAsync(string path, FileMode mode) + { + return this.AppendFromFileAsync(path, mode, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to append a file to an append blob. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + [DoesServiceRequest] + public Task AppendFromFileAsync(string path, FileMode mode, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginAppendFromFile, this.EndAppendFromFile, path, mode, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to append a file to an append blob. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.AppendFromFileAsync(path, mode, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to append a file to an append blob. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginAppendFromFile, this.EndAppendFromFile, path, mode, accessCondition, options, operationContext, cancellationToken); + } +#endif + +#if SYNC + /// + /// Uploads the contents of a byte array to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public void UploadFromByteArray(byte[] buffer, int index, int count, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + CommonUtility.AssertNotNull("buffer", buffer); + + using (SyncMemoryStream stream = new SyncMemoryStream(buffer, index, count)) + { + this.UploadFromStream(stream, accessCondition, options, operationContext); + } + } + + /// + /// Appends the contents of a byte array to an append blob.Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public void AppendFromByteArray(byte[] buffer, int index, int count, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + CommonUtility.AssertNotNull("buffer", buffer); + + using (SyncMemoryStream stream = new SyncMemoryStream(buffer, index, count)) + { + this.AppendFromStream(stream, accessCondition, options, operationContext); + } + } +#endif + + /// + /// Begins an asynchronous operation to upload the contents of a byte array to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginUploadFromByteArray(byte[] buffer, int index, int count, AsyncCallback callback, object state) + { + return this.BeginUploadFromByteArray(buffer, index, count, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to upload the contents of a byte array to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginUploadFromByteArray(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + CommonUtility.AssertNotNull("buffer", buffer); + + SyncMemoryStream stream = new SyncMemoryStream(buffer, index, count); + return this.BeginUploadFromStream(stream, accessCondition, options, operationContext, callback, state); + } + + /// + /// Ends an asynchronous operation to upload the contents of a byte array to an append blob. Recommended only for single-writer scenarios. + /// + /// An that references the pending asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + public void EndUploadFromByteArray(IAsyncResult asyncResult) + { + this.EndUploadFromStream(asyncResult); + } + + /// + /// Begins an asynchronous operation to append the contents of a byte array to an append blob. Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginAppendFromByteArray(byte[] buffer, int index, int count, AsyncCallback callback, object state) + { + return this.BeginAppendFromByteArray(buffer, index, count, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to append the contents of a byte array to an append blob. Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginAppendFromByteArray(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + CommonUtility.AssertNotNull("buffer", buffer); + + SyncMemoryStream stream = new SyncMemoryStream(buffer, index, count); + return this.BeginAppendFromStream(stream, accessCondition, options, operationContext, callback, state); + } + + /// + /// Ends an asynchronous operation to append the contents of a byte array to an append blob. Recommended only for single-writer scenarios. + /// + /// An that references the pending asynchronous operation. + public void EndAppendFromByteArray(IAsyncResult asyncResult) + { + this.EndAppendFromStream(asyncResult); + } + +#if TASK + /// + /// Initiates an asynchronous operation to upload the contents of a byte array to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count) + { + return this.UploadFromByteArrayAsync(buffer, index, count, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to upload the contents of a byte array to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromByteArray, this.EndUploadFromByteArray, buffer, index, count, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to upload the contents of a byte array to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.UploadFromByteArrayAsync(buffer, index, count, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to upload the contents of a byte array to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadFromByteArray, this.EndUploadFromByteArray, buffer, index, count, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to append the contents of a byte array to an append blob. Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + [DoesServiceRequest] + public Task AppendFromByteArrayAsync(byte[] buffer, int index, int count) + { + return this.AppendFromByteArrayAsync(buffer, index, count, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to append the contents of a byte array to an append blob. Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + [DoesServiceRequest] + public Task AppendFromByteArrayAsync(byte[] buffer, int index, int count, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginAppendFromByteArray, this.EndAppendFromByteArray, buffer, index, count, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to append the contents of a byte array to an append blob.This API should be used strictly in a single writer scenario + /// because the API internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple writer scenario. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.AppendFromByteArrayAsync(buffer, index, count, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to upload the contents of a byte array to an append blob.This API should be used strictly in a single writer scenario + /// because the API internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple writer scenario. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginAppendFromByteArray, this.EndAppendFromByteArray, buffer, index, count, accessCondition, options, operationContext, cancellationToken); + } +#endif + +#if SYNC + /// + /// Uploads a string of text to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A string containing the text to upload. + /// A object that indicates the text encoding to use. If null, UTF-8 will be used. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public void UploadText(string content, Encoding encoding = null, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + CommonUtility.AssertNotNull("content", content); + + byte[] contentAsBytes = (encoding ?? Encoding.UTF8).GetBytes(content); + this.UploadFromByteArray(contentAsBytes, 0, contentAsBytes.Length, accessCondition, options, operationContext); + } + + /// + /// Appends a string of text to an append blob. This API should be used strictly in a single writer scenario + /// because the API internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple writer scenario. + /// + /// A string containing the text to upload. + /// A object that indicates the text encoding to use. If null, UTF-8 will be used. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public void AppendText(string content, Encoding encoding = null, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + CommonUtility.AssertNotNull("content", content); + + byte[] contentAsBytes = (encoding ?? Encoding.UTF8).GetBytes(content); + this.AppendFromByteArray(contentAsBytes, 0, contentAsBytes.Length, accessCondition, options, operationContext); + } +#endif + + /// + /// Begins an asynchronous operation to upload a string of text to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A string containing the text to upload. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginUploadText(string content, AsyncCallback callback, object state) + { + return this.BeginUploadText(content, null /* encoding */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to upload a string of text to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A string containing the text to upload. + /// A object that indicates the text encoding to use. If null, UTF-8 will be used. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginUploadText(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + CommonUtility.AssertNotNull("content", content); + + byte[] contentAsBytes = (encoding ?? Encoding.UTF8).GetBytes(content); + return this.BeginUploadFromByteArray(contentAsBytes, 0, contentAsBytes.Length, accessCondition, options, operationContext, callback, state); + } + + /// + /// Ends an asynchronous operation to upload a string of text to an append blob. This API should be used strictly in a single writer scenario + /// because the API internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple writer scenario. + /// + /// An that references the pending asynchronous operation. + public void EndUploadText(IAsyncResult asyncResult) + { + this.EndUploadFromByteArray(asyncResult); + } + + /// + /// Begins an asynchronous operation to append a string of text to an append blob. This API should be used strictly in a single writer scenario + /// because the API internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple writer scenario. + /// + /// A string containing the text to upload. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginAppendText(string content, AsyncCallback callback, object state) + { + return this.BeginAppendText(content, null /* encoding */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to append a string of text to an append blob. This API should be used strictly in a single writer scenario + /// because the API internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple writer scenario. + /// + /// A string containing the text to upload. + /// A object that indicates the text encoding to use. If null, UTF-8 will be used. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginAppendText(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + CommonUtility.AssertNotNull("content", content); + + byte[] contentAsBytes = (encoding ?? Encoding.UTF8).GetBytes(content); + return this.BeginAppendFromByteArray(contentAsBytes, 0, contentAsBytes.Length, accessCondition, options, operationContext, callback, state); + } + + /// + /// Ends an asynchronous operation to append a string of text to an append blob. This API should be used strictly in a single writer scenario + /// because the API internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple writer scenario. + /// + /// An that references the pending asynchronous operation. + public void EndAppendText(IAsyncResult asyncResult) + { + this.EndAppendFromByteArray(asyncResult); + } + +#if TASK + /// + /// Initiates an asynchronous operation to upload a string of text to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A string containing the text to upload. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadTextAsync(string content) + { + return this.UploadTextAsync(content, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to upload a string of text to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A string containing the text to upload. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadTextAsync(string content, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadText, this.EndUploadText, content, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to upload a string of text to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A string containing the text to upload. + /// A object that indicates the text encoding to use. If null, UTF-8 will be used. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadTextAsync(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.UploadTextAsync(content, encoding, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to upload a string of text to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// A string containing the text to upload. + /// A object that indicates the text encoding to use. If null, UTF-8 will be used. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadTextAsync(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadText, this.EndUploadText, content, encoding, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to append a string of text to an append blob. This API should be used strictly in a single writer scenario + /// because the API internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple writer scenario. + /// + /// A string containing the text to upload. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task AppendTextAsync(string content) + { + return this.AppendTextAsync(content, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to append a string of text to an append blob. This API should be used strictly in a single writer scenario + /// because the API internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple writer scenario. + /// + /// A string containing the text to upload. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task AppendTextAsync(string content, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginUploadText, this.EndUploadText, content, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to append a string of text to an append blob. This API should be used strictly in a single writer scenario + /// because the API internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple writer scenario. + /// + /// A string containing the text to upload. + /// A object that indicates the text encoding to use. If null, UTF-8 will be used. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendTextAsync(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.AppendTextAsync(content, encoding, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to append a string of text to an append blob. This API should be used strictly in a single writer scenario + /// because the API internally uses the append-offset conditional header to avoid duplicate blocks which does not work in a multiple writer scenario. + /// + /// A string containing the text to upload. + /// A object that indicates the text encoding to use. If null, UTF-8 will be used. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendTextAsync(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginAppendText, this.EndAppendText, content, encoding, accessCondition, options, operationContext, cancellationToken); + } +#endif + +#if SYNC + /// + /// Creates an empty append blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] + public void CreateOrReplace(AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + this.attributes.AssertNoSnapshot(); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); + Executor.ExecuteSync( + this.CreateImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext); + } +#endif + + /// + /// Begins an asynchronous operation to create an empty append blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, + /// use . + /// + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginCreateOrReplace(AsyncCallback callback, object state) + { + return this.BeginCreateOrReplace(null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to create an empty append blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginCreateOrReplace(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); + return Executor.BeginExecuteAsync( + this.CreateImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + callback, + state); + } + + /// + /// Ends an asynchronous operation to create an append blob. + /// + /// An that references the pending asynchronous operation. + public void EndCreateOrReplace(IAsyncResult asyncResult) + { + Executor.EndExecuteAsync(asyncResult); + } + +#if TASK + /// + /// Initiates an asynchronous operation to create an empty append blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, + /// use . + /// + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task CreateOrReplaceAsync() + { + return this.CreateOrReplaceAsync(CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to create an append blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, + /// use . + /// + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task CreateOrReplaceAsync(CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginCreateOrReplace, this.EndCreateOrReplace, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to create an empty append blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task CreateOrReplaceAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.CreateOrReplaceAsync(accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to create an empty append blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task CreateOrReplaceAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginCreateOrReplace, this.EndCreateOrReplace, accessCondition, options, operationContext, cancellationToken); + } +#endif + +#if SYNC + /// + /// Commits a new block of data to the end of the blob. + /// + /// A object that provides the data for the block. + /// An optional hash value used to ensure transactional integrity for the block. May be null or an empty string. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// The offset at which the block was appended. + /// + /// Clients may send the Content-MD5 header for a given Append Block operation as a means to ensure transactional integrity over the wire. + /// The parameter permits clients who already have access to a pre-computed MD5 value for a given byte range to provide it. + /// If the property is set to true and the parameter is set + /// to null, then the client library will calculate the MD5 value internally. + /// + [DoesServiceRequest] + public long AppendBlock(Stream blockData, string contentMD5 = null, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + CommonUtility.AssertNotNull("blockData", blockData); + + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); + bool requiresContentMD5 = string.IsNullOrEmpty(contentMD5) && modifiedOptions.UseTransactionalMD5.Value; + operationContext = operationContext ?? new OperationContext(); + + Stream seekableStream = blockData; + bool seekableStreamCreated = false; + + try + { + if (!blockData.CanSeek || requiresContentMD5) + { + ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions); + + Stream writeToStream; + if (blockData.CanSeek) + { + writeToStream = Stream.Null; + } + else + { + seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); + seekableStreamCreated = true; + writeToStream = seekableStream; + } + + long startPosition = seekableStream.Position; + StreamDescriptor streamCopyState = new StreamDescriptor(); + blockData.WriteToSync(writeToStream, null /* copyLength */, Constants.MaxBlockSize, requiresContentMD5, true, tempExecutionState, streamCopyState); + seekableStream.Position = startPosition; + + if (requiresContentMD5) + { + contentMD5 = streamCopyState.Md5; + } + } + + return Executor.ExecuteSync( + this.AppendBlockImpl(seekableStream, contentMD5, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext); + } + finally + { + if (seekableStreamCreated) + { + seekableStream.Dispose(); + } + } + } +#endif + + /// + /// Begins an asynchronous operation to commit a new block of data to the end of the blob. + /// + /// A object that provides the data for the block. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginAppendBlock(Stream blockData, AsyncCallback callback, object state) + { + return this.BeginAppendBlock(blockData, null /* contentMD5 */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to commit a new block of data to the end of the blob. + /// + /// A object that provides the data for the block. + /// An optional hash value used to ensure transactional integrity for the block. May be null or an empty string. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Clients may send the Content-MD5 header for a given Append Block operation as a means to ensure transactional integrity over the wire. + /// The parameter permits clients who already have access to a pre-computed MD5 value for a given byte range to provide it. + /// If the property is set to true and the parameter is set + /// to null, then the client library will calculate the MD5 value internally. + /// + [DoesServiceRequest] + public ICancellableAsyncResult BeginAppendBlock(Stream blockData, string contentMD5, AsyncCallback callback, object state) + { + return this.BeginAppendBlock(blockData, contentMD5, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to commit a new block of data to the end of the blob. + /// + /// A object that provides the data for the block. + /// An optional hash value used to ensure transactional integrity for the block. May be null or an empty string. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + /// + /// Clients may send the Content-MD5 header for a given Append Block operation as a means to ensure transactional integrity over the wire. + /// The parameter permits clients who already have access to a pre-computed MD5 value for a given byte range to provide it. + /// If the property is set to true and the parameter is set + /// to null, then the client library will calculate the MD5 value internally. + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Needed to ensure exceptions are not thrown on threadpool threads.")] + [DoesServiceRequest] + public ICancellableAsyncResult BeginAppendBlock(Stream blockData, string contentMD5, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + CommonUtility.AssertNotNull("blockData", blockData); + + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); + bool requiresContentMD5 = string.IsNullOrEmpty(contentMD5) && modifiedOptions.UseTransactionalMD5.Value; + operationContext = operationContext ?? new OperationContext(); + StorageAsyncResult storageAsyncResult = new StorageAsyncResult(callback, state); + + if (blockData.CanSeek && !requiresContentMD5) + { + this.AppendBlockHandler(blockData, contentMD5, accessCondition, modifiedOptions, operationContext, storageAsyncResult); + } + else + { + ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions); + storageAsyncResult.CancelDelegate = tempExecutionState.Cancel; + + Stream seekableStream; + Stream writeToStream; + if (blockData.CanSeek) + { + seekableStream = blockData; + writeToStream = Stream.Null; + } + else + { + seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); + storageAsyncResult.OperationState = seekableStream; + writeToStream = seekableStream; + } + + long startPosition = seekableStream.Position; + StreamDescriptor streamCopyState = new StreamDescriptor(); + blockData.WriteToAsync( + writeToStream, + null /* copyLength */, + Constants.MaxBlockSize, + requiresContentMD5, + tempExecutionState, + streamCopyState, + completedState => + { + storageAsyncResult.UpdateCompletedSynchronously(completedState.CompletedSynchronously); + + if (completedState.ExceptionRef != null) + { + storageAsyncResult.OnComplete(completedState.ExceptionRef); + } + else + { + try + { + if (requiresContentMD5) + { + contentMD5 = streamCopyState.Md5; + } + + seekableStream.Position = startPosition; + this.AppendBlockHandler(seekableStream, contentMD5, accessCondition, modifiedOptions, operationContext, storageAsyncResult); + } + catch (Exception e) + { + storageAsyncResult.OnComplete(e); + } + } + }); + } + + return storageAsyncResult; + } + + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Reviewed.")] + private void AppendBlockHandler(Stream blockData, string contentMD5, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, StorageAsyncResult storageAsyncResult) + { + lock (storageAsyncResult.CancellationLockerObject) + { + ICancellableAsyncResult result = Executor.BeginExecuteAsync( + this.AppendBlockImpl(blockData, contentMD5, accessCondition, options), + options.RetryPolicy, + operationContext, + ar => + { + storageAsyncResult.UpdateCompletedSynchronously(ar.CompletedSynchronously); + + try + { + storageAsyncResult.Result = Executor.EndExecuteAsync(ar); + storageAsyncResult.OnComplete(); + } + catch (Exception e) + { + storageAsyncResult.OnComplete(e); + } + }, + null /* asyncState */); + + storageAsyncResult.CancelDelegate = result.Cancel; + if (storageAsyncResult.CancelRequested) + { + storageAsyncResult.Cancel(); + } + } + } + + /// + /// Ends an asynchronous operation to commit a new block of data to the end of the blob. + /// + /// An that references the pending asynchronous operation. + public long EndAppendBlock(IAsyncResult asyncResult) + { + StorageAsyncResult storageAsyncResult = (StorageAsyncResult)asyncResult; + + try + { + storageAsyncResult.End(); + return storageAsyncResult.Result; + } + finally + { + if (storageAsyncResult.OperationState != null) + { + MultiBufferMemoryStream stream = (MultiBufferMemoryStream)storageAsyncResult.OperationState; + stream.Dispose(); + } + } + } + +#if TASK + /// + /// Initiates an asynchronous operation to commit a new block of data to the end of the blob. + /// + /// A object that provides the data for the block. + /// An optional hash value used to ensure transactional integrity for the block. May be null or an empty string. + /// A object that represents the asynchronous operation. + /// + /// Clients may send the Content-MD5 header for a given Append Block operation as a means to ensure transactional integrity over the wire. + /// The parameter permits clients who already have access to a pre-computed MD5 value for a given byte range to provide it. + /// If the property is set to true and the parameter is set + /// to null, then the client library will calculate the MD5 value internally. + /// + [DoesServiceRequest] + public Task AppendBlockAsync(Stream blockData, string contentMD5 = null) + { + return this.AppendBlockAsync(blockData, contentMD5, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to commit a new block of data to the end of the blob. + /// + /// A object that provides the data for the block. + /// An optional hash value used to ensure transactional integrity for the block. May be null or an empty string. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Clients may send the Content-MD5 header for a given Put Block operation as a means to ensure transactional integrity over the wire. + /// The parameter permits clients who already have access to a pre-computed MD5 value for a given byte range to provide it. + /// If the property is set to true and the parameter is set + /// to null, then the client library will calculate the MD5 value internally. + /// + [DoesServiceRequest] + public Task AppendBlockAsync(Stream blockData, string contentMD5, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginAppendBlock, this.EndAppendBlock, blockData, contentMD5, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to commit a new block of data to the end of the blob. + /// + /// A object that provides the data for the block. + /// An optional hash value used to ensure transactional integrity for the block. May be null or an empty string. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + /// + /// Clients may send the Content-MD5 header for a given Append Block operation as a means to ensure transactional integrity over the wire. + /// The parameter permits clients who already have access to a pre-computed MD5 value for a given byte range to provide it. + /// If the property is set to true and the parameter is set + /// to null, then the client library will calculate the MD5 value internally. + /// + [DoesServiceRequest] + public Task AppendBlockAsync(Stream blockData, string contentMD5, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.AppendBlockAsync(blockData, contentMD5, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to commit a new block of data to the end of the blob. + /// + /// A object that provides the data for the block. + /// An optional hash value used to ensure transactional integrity for the block. May be null or an empty string. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + /// + /// Clients may send the Content-MD5 header for a given Append Block operation as a means to ensure transactional integrity over the wire. + /// The parameter permits clients who already have access to a pre-computed MD5 value for a given byte range to provide it. + /// If the property is set to true and the parameter is set + /// to null, then the client library will calculate the MD5 value internally. + /// + [DoesServiceRequest] + public Task AppendBlockAsync(Stream blockData, string contentMD5, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginAppendBlock, this.EndAppendBlock, blockData, contentMD5, accessCondition, options, operationContext, cancellationToken); + } +#endif + +#if SYNC + /// + /// Downloads the blob's contents as a string. + /// + /// An object that indicates the text encoding to use. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// The contents of the blob, as a string. + public string DownloadText(Encoding encoding = null, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + using (SyncMemoryStream stream = new SyncMemoryStream()) + { + this.DownloadToStream(stream, accessCondition, options, operationContext); + byte[] streamAsBytes = stream.GetBuffer(); + return (encoding ?? Encoding.UTF8).GetString(streamAsBytes, 0, (int)stream.Length); + } + } +#endif + + /// + /// Begins an asynchronous operation to download the blob's contents as a string. + /// + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + public ICancellableAsyncResult BeginDownloadText(AsyncCallback callback, object state) + { + return this.BeginDownloadText(null /* encoding */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to download the blob's contents as a string. + /// + /// An object that indicates the text encoding to use. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + public ICancellableAsyncResult BeginDownloadText(Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + SyncMemoryStream stream = new SyncMemoryStream(); + StorageAsyncResult storageAsyncResult = new StorageAsyncResult(callback, state) { OperationState = Tuple.Create(stream, encoding) }; + + ICancellableAsyncResult result = this.BeginDownloadToStream( + stream, + accessCondition, + options, + operationContext, + this.DownloadTextCallback, + storageAsyncResult); + + storageAsyncResult.CancelDelegate = result.Cancel; + return storageAsyncResult; + } + + /// + /// Called when the asynchronous DownloadToStream operation completes. + /// + /// The result of the asynchronous operation. + private void DownloadTextCallback(IAsyncResult asyncResult) + { + StorageAsyncResult storageAsyncResult = (StorageAsyncResult)asyncResult.AsyncState; + + try + { + this.EndDownloadToStream(asyncResult); + + Tuple state = (Tuple)storageAsyncResult.OperationState; + byte[] streamAsBytes = state.Item1.GetBuffer(); + storageAsyncResult.Result = (state.Item2 ?? Encoding.UTF8).GetString(streamAsBytes, 0, (int)state.Item1.Length); + storageAsyncResult.OnComplete(); + } + catch (Exception e) + { + storageAsyncResult.OnComplete(e); + } + } + + /// + /// Ends an asynchronous operation to download the blob's contents as a string. + /// + /// An that references the pending asynchronous operation. + /// The contents of the blob, as a string. + public string EndDownloadText(IAsyncResult asyncResult) + { + StorageAsyncResult res = (StorageAsyncResult)asyncResult; + res.End(); + return res.Result; + } + +#if TASK + /// + /// Initiates an asynchronous operation to download the blob's contents as a string. + /// + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task DownloadTextAsync() + { + return this.DownloadTextAsync(CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to download the blob's contents as a string. + /// + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task DownloadTextAsync(CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginDownloadText, this.EndDownloadText, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to download the blob's contents as a string. + /// + /// An object that indicates the text encoding to use. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task DownloadTextAsync(Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.DownloadTextAsync(encoding, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to download the blob's contents as a string. + /// + /// An object that indicates the text encoding to use. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task DownloadTextAsync(Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginDownloadText, this.EndDownloadText, encoding, accessCondition, options, operationContext, cancellationToken); + } +#endif + +#if SYNC + /// + /// Begins an operation to start copying another append blob's contents, properties, and metadata to this append blob. + /// + /// The of the source blob. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the blob's ETag, last-modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] + public string StartCopy(CloudAppendBlob source, AccessCondition sourceAccessCondition = null, AccessCondition destAccessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + return this.StartCopy(CloudBlob.SourceBlobToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext); + } + +#endif + /// + /// Begins an asynchronous operation to start copying another append blob's contents, properties, and metadata to this append blob. + /// + /// A object. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginStartCopy(CloudAppendBlob source, AsyncCallback callback, object state) + { + return this.BeginStartCopy(CloudBlob.SourceBlobToUri(source), callback, state); + } + + /// + /// Begins an asynchronous operation to start copying another append blob's contents, properties, and metadata to this append blob. + /// + /// A object. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginStartCopy(CloudAppendBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginStartCopy(CloudBlob.SourceBlobToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext, callback, state); + } + +#if TASK + /// + /// Initiates an asynchronous operation to start copying another append blob's contents, properties, and metadata to this append blob. + /// + /// A object. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudAppendBlob source) + { + return this.StartCopyAsync(source, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to start copying another append blob's contents, properties, and metadata to this append blob. + /// + /// A object. + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudAppendBlob source, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to start copying another append blob's contents, properties, and metadata to this append blob. + /// + /// A object. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudAppendBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.StartCopyAsync(source, sourceAccessCondition, destAccessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to start copying another append blob's contents, properties, and metadata to this append blob. + /// + /// A object. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudAppendBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); + } +#endif + +#if SYNC + /// + /// Creates a snapshot of the blob. + /// + /// A collection of name-value pairs defining the metadata of the snapshot. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request, or null. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// A object that is a blob snapshot. + [DoesServiceRequest] + public CloudAppendBlob CreateSnapshot(IDictionary metadata = null, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + this.attributes.AssertNoSnapshot(); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); + return Executor.ExecuteSync( + this.CreateSnapshotImpl(metadata, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext); + } +#endif + + /// + /// Begins an asynchronous operation to create a snapshot of the blob. + /// + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginCreateSnapshot(AsyncCallback callback, object state) + { + return this.BeginCreateSnapshot(null /* metadata */, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to create a snapshot of the blob. + /// + /// A collection of name-value pairs defining the metadata of the snapshot. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request, or null. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginCreateSnapshot(IDictionary metadata, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + this.attributes.AssertNoSnapshot(); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); + return Executor.BeginExecuteAsync( + this.CreateSnapshotImpl(metadata, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + callback, + state); + } + + /// + /// Ends an asynchronous operation to create a snapshot of the blob. + /// + /// An that references the pending asynchronous operation. + /// A object that is a blob snapshot. + public CloudAppendBlob EndCreateSnapshot(IAsyncResult asyncResult) + { + return Executor.EndExecuteAsync(asyncResult); + } + +#if TASK + /// + /// Initiates an asynchronous operation to create a snapshot of the blob. + /// + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task CreateSnapshotAsync() + { + return this.CreateSnapshotAsync(CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to create a snapshot of the blob. + /// + /// A to observe while waiting for a task to complete. + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task CreateSnapshotAsync(CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginCreateSnapshot, this.EndCreateSnapshot, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to create a snapshot of the blob. + /// + /// A collection of name-value pairs defining the metadata of the snapshot. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task CreateSnapshotAsync(IDictionary metadata, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.CreateSnapshotAsync(metadata, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to create a snapshot of the blob. + /// + /// A collection of name-value pairs defining the metadata of the snapshot. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task CreateSnapshotAsync(IDictionary metadata, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginCreateSnapshot, this.EndCreateSnapshot, metadata, accessCondition, options, operationContext, cancellationToken); + } +#endif + + /// + /// Implements the Create method. + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// A that creates the blob. + private RESTCommand CreateImpl(AccessCondition accessCondition, BlobRequestOptions options) + { + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.Put(uri, serverTimeout, this.Properties, BlobType.AppendBlob, 0, accessCondition, useVersionHeader, ctx); + putCmd.SetHeaders = (r, ctx) => BlobHttpWebRequestFactory.AddMetadata(r, this.Metadata); + putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); + CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); + this.Properties.Length = 0; + return NullType.Value; + }; + + return putCmd; + } + + /// + /// Commits the block to the end of the blob. + /// + /// The source stream. + /// The content MD5. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// A that commits the block to the end of the blob. + internal RESTCommand AppendBlockImpl(Stream source, string contentMD5, AccessCondition accessCondition, BlobRequestOptions options) + { + options.AssertNoEncryptionPolicyOrStrictMode(); + + long offset = source.Position; + + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.SendStream = source; + putCmd.RecoveryAction = (cmd, ex, ctx) => RecoveryActions.SeekStream(cmd, offset); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.AppendBlock(uri, serverTimeout, accessCondition, useVersionHeader, ctx); + putCmd.SetHeaders = (r, ctx) => + { + if (!string.IsNullOrEmpty(contentMD5)) + { + r.Headers[HttpRequestHeader.ContentMd5] = contentMD5; + } + }; + putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + long appendOffset = -1; + if (resp.Headers.AllKeys.Contains(Constants.HeaderConstants.BlobAppendOffset)) + { + appendOffset = long.Parse(resp.Headers[Constants.HeaderConstants.BlobAppendOffset], CultureInfo.InvariantCulture); + } + + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, appendOffset, cmd, ex); + CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); + return appendOffset; + }; + + return putCmd; + } + + /// + /// Implementation for the Snapshot method. + /// + /// A collection of name-value pairs defining the metadata of the snapshot, or null. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// A that creates the snapshot. + /// If the metadata parameter is null then no metadata is associated with the request. + internal RESTCommand CreateSnapshotImpl(IDictionary metadata, AccessCondition accessCondition, BlobRequestOptions options) + { + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => BlobHttpWebRequestFactory.Snapshot(uri, serverTimeout, accessCondition, useVersionHeader, ctx); + putCmd.SetHeaders = (r, ctx) => + { + if (metadata != null) + { + BlobHttpWebRequestFactory.AddMetadata(r, metadata); + } + }; + putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, null /* retVal */, cmd, ex); + DateTimeOffset snapshotTime = NavigationHelper.ParseSnapshotTime(BlobHttpResponseParsers.GetSnapshotTime(resp)); + + CloudAppendBlob snapshot = new CloudAppendBlob(this.Name, snapshotTime, this.Container); + snapshot.attributes.Metadata = new Dictionary(metadata ?? this.Metadata); + snapshot.attributes.Properties = new BlobProperties(this.Properties); + CloudBlob.UpdateETagLMTLengthAndSequenceNumber(snapshot.attributes, resp, false); + return snapshot; + }; + + return putCmd; + } + } +} diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudBlob.cs index 92105b83d..0f81848ca 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlob.cs @@ -30,7 +30,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob using System.Threading.Tasks; /// - /// Represents a blob object. + /// Represents a blob. /// public partial class CloudBlob : IListBlobItem { @@ -2415,6 +2415,7 @@ public Task BreakLeaseAsync(TimeSpan? breakPeriod, AccessCondition acc /// This method fetches the blob's ETag, last-modified time, and part of the copy state. /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. /// + [Obsolete("Deprecated this method in favor of StartCopy.")] [DoesServiceRequest] public string StartCopyFromBlob(Uri source, AccessCondition sourceAccessCondition = null, AccessCondition destAccessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) { @@ -2429,6 +2430,7 @@ public string StartCopyFromBlob(Uri source, AccessCondition sourceAccessConditio /// An delegate that will receive notification when the asynchronous operation completes. /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. + [Obsolete("Deprecated this method in favor of BeginStartCopy.")] [DoesServiceRequest] public ICancellableAsyncResult BeginStartCopyFromBlob(Uri source, AsyncCallback callback, object state) { @@ -2446,6 +2448,7 @@ public ICancellableAsyncResult BeginStartCopyFromBlob(Uri source, AsyncCallback /// An delegate that will receive notification when the asynchronous operation completes. /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. + [Obsolete("Deprecated this method in favor of BeginStartCopy.")] [DoesServiceRequest] public ICancellableAsyncResult BeginStartCopyFromBlob(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { @@ -2461,6 +2464,7 @@ public ICancellableAsyncResult BeginStartCopyFromBlob(Uri source, AccessConditio /// This method fetches the blob's ETag, last-modified time, and part of the copy state. /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. /// + [Obsolete("Deprecated this method in favor of EndStartCopy.")] public string EndStartCopyFromBlob(IAsyncResult asyncResult) { return Executor.EndExecuteAsync(asyncResult); @@ -2473,6 +2477,7 @@ public string EndStartCopyFromBlob(IAsyncResult asyncResult) /// /// The of the source blob. /// A object of type string that represents the asynchronous operation. + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(Uri source) { @@ -2486,6 +2491,7 @@ public Task StartCopyFromBlobAsync(Uri source) /// The of the source blob. /// A to observe while waiting for a task to complete. /// A object of type string that represents the asynchronous operation. + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(Uri source, CancellationToken cancellationToken) { @@ -2502,6 +2508,7 @@ public Task StartCopyFromBlobAsync(Uri source, CancellationToken cancell /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A object of type string that represents the asynchronous operation. + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -2519,6 +2526,7 @@ public Task StartCopyFromBlobAsync(Uri source, AccessCondition sourceAcc /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. /// A object of type string that represents the asynchronous operation. + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -2928,6 +2936,9 @@ private RESTCommand GetBlobImpl(BlobAttributes blobAttributes, Stream long? endOffset = null; bool bufferIV = false; long? userSpecifiedLength = length; + + options.AssertPolicyIfRequired(); + if (isRangeGet && options.EncryptionPolicy != null) { #if WINDOWS_PHONE @@ -3584,6 +3595,7 @@ internal static void UpdateETagLMTLengthAndSequenceNumber(BlobAttributes blobAtt blobAttributes.Properties.ETag = parsedProperties.ETag ?? blobAttributes.Properties.ETag; blobAttributes.Properties.LastModified = parsedProperties.LastModified ?? blobAttributes.Properties.LastModified; blobAttributes.Properties.PageBlobSequenceNumber = parsedProperties.PageBlobSequenceNumber ?? blobAttributes.Properties.PageBlobSequenceNumber; + blobAttributes.Properties.AppendBlobCommittedBlockCount = parsedProperties.AppendBlobCommittedBlockCount ?? blobAttributes.Properties.AppendBlobCommittedBlockCount; if (updateLength) { diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlobClient.cs b/Lib/ClassLibraryCommon/Blob/CloudBlobClient.cs index e9934c572..076433a49 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlobClient.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlobClient.cs @@ -808,6 +808,9 @@ private RESTCommand GetBlobReferenceImpl(StorageUri blobUri, AccessC case BlobType.PageBlob: return new CloudPageBlob(attributes, client); + case BlobType.AppendBlob: + return new CloudAppendBlob(attributes, client); + default: throw new InvalidOperationException(); } @@ -1179,6 +1182,7 @@ private RESTCommand SetServicePropertiesImpl(ServiceProperties propert } catch (InvalidOperationException invalidOpException) { + str.Dispose(); throw new ArgumentException(invalidOpException.Message, "properties"); } @@ -1186,6 +1190,7 @@ private RESTCommand SetServicePropertiesImpl(ServiceProperties propert RESTCommand retCmd = new RESTCommand(this.Credentials, this.StorageUri); retCmd.SendStream = str; + retCmd.StreamToDispose = str; retCmd.BuildRequestDelegate = BlobHttpWebRequestFactory.SetServiceProperties; retCmd.RecoveryAction = RecoveryActions.RewindStream; retCmd.SignRequest = this.AuthenticationHandler.SignRequest; diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlobContainer.cs b/Lib/ClassLibraryCommon/Blob/CloudBlobContainer.cs index 273af3e4f..7ceac388e 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlobContainer.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlobContainer.cs @@ -2657,6 +2657,7 @@ private RESTCommand SetPermissionsImpl(BlobContainerPermissions acl, A options.ApplyToStorageCommand(putCmd); putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => ContainerHttpWebRequestFactory.SetAcl(uri, serverTimeout, acl.PublicAccess, accessCondition, useVersionHeader, ctx); putCmd.SendStream = memoryStream; + putCmd.StreamToDispose = memoryStream; putCmd.RecoveryAction = RecoveryActions.RewindStream; putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => @@ -2725,6 +2726,10 @@ private IListBlobItem SelectListBlobItem(IListBlobEntry protocolItem) { return new CloudPageBlob(attributes, this.ServiceClient); } + else if (attributes.Properties.BlobType == BlobType.AppendBlob) + { + return new CloudAppendBlob(attributes, this.ServiceClient); + } else { throw new InvalidOperationException(SR.InvalidBlobListItem); diff --git a/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs index 5d9a30ab2..9978f4b19 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudBlockBlob.cs @@ -21,6 +21,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Executor; using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.File; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Collections.Generic; @@ -40,7 +41,7 @@ public sealed partial class CloudBlockBlob : CloudBlob, ICloudBlob { #if SYNC /// - /// Opens a stream for writing to the blob. + /// Opens a stream for writing to the blob. If the blob already exists, it will be overwritten. /// /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. If null, default options are applied to the request. @@ -50,6 +51,8 @@ public sealed partial class CloudBlockBlob : CloudBlob, ICloudBlob /// Note that this method always makes a call to the method under the covers. /// Set the property before calling this method to specify the block size to write, in bytes, /// ranging from between 16 KB and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, pass in an + /// object generated using . /// public CloudBlobStream OpenWrite(AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) { @@ -78,21 +81,24 @@ public CloudBlobStream OpenWrite(AccessCondition accessCondition = null, BlobReq } } +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + modifiedOptions.AssertPolicyIfRequired(); + if (modifiedOptions.EncryptionPolicy != null) { ICryptoTransform transform = modifiedOptions.EncryptionPolicy.CreateAndSetEncryptionContext(this.Metadata, false /* noPadding */); return new BlobEncryptedWriteStream(this, accessCondition, modifiedOptions, operationContext, transform); } else +#endif { return new BlobWriteStream(this, accessCondition, modifiedOptions, operationContext); } } - #endif /// - /// Begins an asynchronous operation to open a stream for writing to the blob. + /// Begins an asynchronous operation to open a stream for writing to the blob. If the blob already exists, it will be overwritten. /// /// An delegate that will receive notification when the asynchronous operation completes. /// A user-defined object that will be passed to the callback delegate. @@ -101,6 +107,7 @@ public CloudBlobStream OpenWrite(AccessCondition accessCondition = null, BlobReq /// Note that this method always makes a call to the method under the covers. /// Set the property before calling this method to specify the block size to write, in bytes, /// ranging from between 16 KB and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, see . /// public ICancellableAsyncResult BeginOpenWrite(AsyncCallback callback, object state) { @@ -108,7 +115,7 @@ public ICancellableAsyncResult BeginOpenWrite(AsyncCallback callback, object sta } /// - /// Begins an asynchronous operation to open a stream for writing to the blob. + /// Begins an asynchronous operation to open a stream for writing to the blob. If the blob already exists, it will be overwritten. /// /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. @@ -120,6 +127,8 @@ public ICancellableAsyncResult BeginOpenWrite(AsyncCallback callback, object sta /// Note that this method always makes a call to the method under the covers. /// Set the property before calling this method to specify the block size to write, in bytes, /// ranging from between 16 KB and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, pass in an + /// object generated using . /// public ICancellableAsyncResult BeginOpenWrite(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { @@ -172,12 +181,16 @@ public ICancellableAsyncResult BeginOpenWrite(AccessCondition accessCondition, B } else { +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + modifiedOptions.AssertPolicyIfRequired(); + if (modifiedOptions.EncryptionPolicy != null) { ICryptoTransform transform = modifiedOptions.EncryptionPolicy.CreateAndSetEncryptionContext(this.Metadata, false /* noPadding */); storageAsyncResult.Result = new BlobEncryptedWriteStream(this, accessCondition, modifiedOptions, operationContext, transform); } else +#endif { storageAsyncResult.Result = new BlobWriteStream(this, accessCondition, modifiedOptions, operationContext); } @@ -202,13 +215,14 @@ public CloudBlobStream EndOpenWrite(IAsyncResult asyncResult) #if TASK /// - /// Initiates an asynchronous operation to open a stream for writing to the blob. + /// Initiates an asynchronous operation to open a stream for writing to the blob. If the blob already exists, it will be overwritten. /// /// A object of type that represents the asynchronous operation. /// /// Note that this method always makes a call to the method under the covers. /// Set the property before calling this method to specify the block size to write, in bytes, /// ranging from between 16 KB and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, see . /// [DoesServiceRequest] public Task OpenWriteAsync() @@ -217,7 +231,7 @@ public Task OpenWriteAsync() } /// - /// Initiates an asynchronous operation to open a stream for writing to the blob. + /// Initiates an asynchronous operation to open a stream for writing to the blob. If the blob already exists, it will be overwritten. /// /// A to observe while waiting for a task to complete. /// A object of type that represents the asynchronous operation. @@ -225,6 +239,7 @@ public Task OpenWriteAsync() /// Note that this method always makes a call to the method under the covers. /// Set the property before calling this method to specify the block size to write, in bytes, /// ranging from between 16 KB and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, see . /// [DoesServiceRequest] public Task OpenWriteAsync(CancellationToken cancellationToken) @@ -233,7 +248,7 @@ public Task OpenWriteAsync(CancellationToken cancellationToken) } /// - /// Initiates an asynchronous operation to open a stream for writing to the blob. + /// Initiates an asynchronous operation to open a stream for writing to the blob. If the blob already exists, it will be overwritten. /// /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. @@ -243,6 +258,8 @@ public Task OpenWriteAsync(CancellationToken cancellationToken) /// Note that this method always makes a call to the method under the covers. /// Set the property before calling this method to specify the block size to write, in bytes, /// ranging from between 16 KB and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, pass in an + /// object generated using . /// [DoesServiceRequest] public Task OpenWriteAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) @@ -251,7 +268,7 @@ public Task OpenWriteAsync(AccessCondition accessCondition, Blo } /// - /// Initiates an asynchronous operation to open a stream for writing to the blob. + /// Initiates an asynchronous operation to open a stream for writing to the blob. If the blob already exists, it will be overwritten. /// /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. /// A object that specifies additional options for the request. @@ -262,6 +279,8 @@ public Task OpenWriteAsync(AccessCondition accessCondition, Blo /// Note that this method always makes a call to the method under the covers. /// Set the property before calling this method to specify the block size to write, in bytes, /// ranging from between 16 KB and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, pass in an + /// object generated using . /// [DoesServiceRequest] public Task OpenWriteAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) @@ -272,7 +291,7 @@ public Task OpenWriteAsync(AccessCondition accessCondition, Blo #if SYNC /// - /// Uploads a stream to a block blob. + /// Uploads a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -285,7 +304,7 @@ public void UploadFromStream(Stream source, AccessCondition accessCondition = nu } /// - /// Uploads a stream to a block blob. + /// Uploads a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -299,7 +318,7 @@ public void UploadFromStream(Stream source, long length, AccessCondition accessC } /// - /// Uploads a stream to a block blob. + /// Uploads a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -328,6 +347,8 @@ internal void UploadFromStreamHelper(Stream source, long? length, AccessConditio bool lessThanSingleBlobThreshold = source.CanSeek && (length ?? source.Length - source.Position) <= modifiedOptions.SingleBlobUploadThresholdInBytes.Value; + modifiedOptions.AssertPolicyIfRequired(); + if (modifiedOptions.ParallelOperationThreadCount.Value == 1 && lessThanSingleBlobThreshold && modifiedOptions.EncryptionPolicy == null) { string contentMD5 = null; @@ -371,7 +392,7 @@ internal void UploadFromStreamHelper(Stream source, long? length, AccessConditio #endif /// - /// Begins an asynchronous operation to upload a stream to a block blob. + /// Begins an asynchronous operation to upload a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// An delegate that will receive notification when the asynchronous operation completes. @@ -384,7 +405,7 @@ public ICancellableAsyncResult BeginUploadFromStream(Stream source, AsyncCallbac } /// - /// Begins an asynchronous operation to upload a stream to a block blob. + /// Begins an asynchronous operation to upload a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -400,7 +421,7 @@ public ICancellableAsyncResult BeginUploadFromStream(Stream source, AccessCondit } /// - /// Begins an asynchronous operation to upload a stream to a block blob. + /// Begins an asynchronous operation to upload a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -414,7 +435,7 @@ public ICancellableAsyncResult BeginUploadFromStream(Stream source, long length, } /// - /// Begins an asynchronous operation to upload a stream to a block blob. + /// Begins an asynchronous operation to upload a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -431,7 +452,7 @@ public ICancellableAsyncResult BeginUploadFromStream(Stream source, long length, } /// - /// Begins an asynchronous operation to upload a stream to a block blob. + /// Begins an asynchronous operation to upload a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -464,6 +485,8 @@ internal ICancellableAsyncResult BeginUploadFromStreamHelper(Stream source, long bool lessThanSingleBlobThreshold = source.CanSeek && (length ?? source.Length - source.Position) <= modifiedOptions.SingleBlobUploadThresholdInBytes.Value; + modifiedOptions.AssertPolicyIfRequired(); + if (modifiedOptions.ParallelOperationThreadCount.Value == 1 && lessThanSingleBlobThreshold && modifiedOptions.EncryptionPolicy == null) { if (modifiedOptions.StoreBlobContentMD5.Value) @@ -652,7 +675,7 @@ public void EndUploadFromStream(IAsyncResult asyncResult) #if TASK /// - /// Initiates an asynchronous operation to upload a stream to a block blob. + /// Initiates an asynchronous operation to upload a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// A object that represents the asynchronous operation. @@ -663,7 +686,7 @@ public Task UploadFromStreamAsync(Stream source) } /// - /// Initiates an asynchronous operation to upload a stream to a block blob. + /// Initiates an asynchronous operation to upload a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// A to observe while waiting for a task to complete. @@ -675,7 +698,7 @@ public Task UploadFromStreamAsync(Stream source, CancellationToken cancellationT } /// - /// Initiates an asynchronous operation to upload a stream to a block blob. + /// Initiates an asynchronous operation to upload a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -689,7 +712,7 @@ public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition } /// - /// Initiates an asynchronous operation to upload a stream to a block blob. + /// Initiates an asynchronous operation to upload a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -704,7 +727,7 @@ public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition } /// - /// Initiates an asynchronous operation to upload a stream to a block blob. + /// Initiates an asynchronous operation to upload a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -716,7 +739,7 @@ public Task UploadFromStreamAsync(Stream source, long length) } /// - /// Initiates an asynchronous operation to upload a stream to a block blob. + /// Initiates an asynchronous operation to upload a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -729,7 +752,7 @@ public Task UploadFromStreamAsync(Stream source, long length, CancellationToken } /// - /// Initiates an asynchronous operation to upload a stream to a block blob. + /// Initiates an asynchronous operation to upload a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -744,7 +767,7 @@ public Task UploadFromStreamAsync(Stream source, long length, AccessCondition ac } /// - /// Initiates an asynchronous operation to upload a stream to a block blob. + /// Initiates an asynchronous operation to upload a stream to a block blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -762,7 +785,7 @@ public Task UploadFromStreamAsync(Stream source, long length, AccessCondition ac #if SYNC /// - /// Uploads a file to the Blob service. + /// Uploads a file to the Blob service. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -782,7 +805,7 @@ public void UploadFromFile(string path, FileMode mode, AccessCondition accessCon #endif /// - /// Begins an asynchronous operation to upload a file to a blob. + /// Begins an asynchronous operation to upload a file to a blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -796,7 +819,7 @@ public ICancellableAsyncResult BeginUploadFromFile(string path, FileMode mode, A } /// - /// Begins an asynchronous operation to upload a file to a blob. + /// Begins an asynchronous operation to upload a file to a blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -875,7 +898,7 @@ public void EndUploadFromFile(IAsyncResult asyncResult) #if TASK /// - /// Initiates an asynchronous operation to upload a file to a blob. + /// Initiates an asynchronous operation to upload a file to a blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -887,7 +910,7 @@ public Task UploadFromFileAsync(string path, FileMode mode) } /// - /// Initiates an asynchronous operation to upload a file to a blob. + /// Initiates an asynchronous operation to upload a file to a blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -900,7 +923,7 @@ public Task UploadFromFileAsync(string path, FileMode mode, CancellationToken ca } /// - /// Initiates an asynchronous operation to upload a file to a blob. + /// Initiates an asynchronous operation to upload a file to a blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -915,7 +938,7 @@ public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition acce } /// - /// Initiates an asynchronous operation to upload a file to a blob. + /// Initiates an asynchronous operation to upload a file to a blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -933,7 +956,7 @@ public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition acce #if SYNC /// - /// Uploads the contents of a byte array to a blob. + /// Uploads the contents of a byte array to a blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -954,7 +977,7 @@ public void UploadFromByteArray(byte[] buffer, int index, int count, AccessCondi #endif /// - /// Begins an asynchronous operation to upload the contents of a byte array to a blob. + /// Begins an asynchronous operation to upload the contents of a byte array to a blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -969,7 +992,7 @@ public ICancellableAsyncResult BeginUploadFromByteArray(byte[] buffer, int index } /// - /// Begins an asynchronous operation to upload the contents of a byte array to a blob. + /// Begins an asynchronous operation to upload the contents of a byte array to a blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -1000,7 +1023,7 @@ public void EndUploadFromByteArray(IAsyncResult asyncResult) #if TASK /// - /// Initiates an asynchronous operation to upload the contents of a byte array to a blob. + /// Initiates an asynchronous operation to upload the contents of a byte array to a blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -1013,7 +1036,7 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count) } /// - /// Initiates an asynchronous operation to upload the contents of a byte array to a blob. + /// Initiates an asynchronous operation to upload the contents of a byte array to a blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -1027,7 +1050,7 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, Cancel } /// - /// Initiates an asynchronous operation to upload the contents of a byte array to a blob. + /// Initiates an asynchronous operation to upload the contents of a byte array to a blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -1043,7 +1066,7 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, Access } /// - /// Initiates an asynchronous operation to upload the contents of a byte array to a blob. + /// Initiates an asynchronous operation to upload the contents of a byte array to a blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -1062,7 +1085,7 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, Access #if SYNC /// - /// Uploads a string of text to a blob. + /// Uploads a string of text to a blob. If the blob already exists, it will be overwritten. /// /// A string containing the text to upload. /// A object that indicates the text encoding to use. If null, UTF-8 will be used. @@ -1080,7 +1103,7 @@ public void UploadText(string content, Encoding encoding = null, AccessCondition #endif /// - /// Begins an asynchronous operation to upload a string of text to a blob. + /// Begins an asynchronous operation to upload a string of text to a blob. If the blob already exists, it will be overwritten. /// /// A string containing the text to upload. /// An delegate that will receive notification when the asynchronous operation completes. @@ -1093,7 +1116,7 @@ public ICancellableAsyncResult BeginUploadText(string content, AsyncCallback cal } /// - /// Begins an asynchronous operation to upload a string of text to a blob. + /// Begins an asynchronous operation to upload a string of text to a blob. If the blob already exists, it will be overwritten. /// /// A string containing the text to upload. /// A object that indicates the text encoding to use. If null, UTF-8 will be used. @@ -1123,7 +1146,7 @@ public void EndUploadText(IAsyncResult asyncResult) #if TASK /// - /// Initiates an asynchronous operation to upload a string of text to a blob. + /// Initiates an asynchronous operation to upload a string of text to a blob. If the blob already exists, it will be overwritten. /// /// A string containing the text to upload. /// A object that represents the asynchronous operation. @@ -1134,7 +1157,7 @@ public Task UploadTextAsync(string content) } /// - /// Initiates an asynchronous operation to upload a string of text to a blob. + /// Initiates an asynchronous operation to upload a string of text to a blob. If the blob already exists, it will be overwritten. /// /// A string containing the text to upload. /// A to observe while waiting for a task to complete. @@ -1146,7 +1169,7 @@ public Task UploadTextAsync(string content, CancellationToken cancellationToken) } /// - /// Initiates an asynchronous operation to upload a string of text to a blob. + /// Initiates an asynchronous operation to upload a string of text to a blob. If the blob already exists, it will be overwritten. /// /// A string containing the text to upload. /// A object that indicates the text encoding to use. If null, UTF-8 will be used. @@ -1161,7 +1184,7 @@ public Task UploadTextAsync(string content, Encoding encoding, AccessCondition a } /// - /// Initiates an asynchronous operation to upload a string of text to a blob. + /// Initiates an asynchronous operation to upload a string of text to a blob. If the blob already exists, it will be overwritten. /// /// A string containing the text to upload. /// A object that indicates the text encoding to use. If null, UTF-8 will be used. @@ -1176,6 +1199,7 @@ public Task UploadTextAsync(string content, Encoding encoding, AccessCondition a return AsyncExtensions.TaskFromVoidApm(this.BeginUploadText, this.EndUploadText, content, encoding, accessCondition, options, operationContext, cancellationToken); } #endif + #if SYNC /// /// Downloads the blob's contents as a string. @@ -1347,36 +1371,49 @@ public void PutBlock(string blockId, Stream blockData, string contentMD5, Access operationContext = operationContext ?? new OperationContext(); Stream seekableStream = blockData; - if (!blockData.CanSeek || requiresContentMD5) - { - ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions); + bool seekableStreamCreated = false; - Stream writeToStream; - if (blockData.CanSeek) - { - writeToStream = Stream.Null; - } - else + try + { + if (!blockData.CanSeek || requiresContentMD5) { - seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); - writeToStream = seekableStream; - } + ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions); - long startPosition = seekableStream.Position; - StreamDescriptor streamCopyState = new StreamDescriptor(); - blockData.WriteToSync(writeToStream, null /* copyLength */, Constants.MaxBlockSize, requiresContentMD5, true, tempExecutionState, streamCopyState); - seekableStream.Position = startPosition; + Stream writeToStream; + if (blockData.CanSeek) + { + writeToStream = Stream.Null; + } + else + { + seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); + seekableStreamCreated = true; + writeToStream = seekableStream; + } - if (requiresContentMD5) + long startPosition = seekableStream.Position; + StreamDescriptor streamCopyState = new StreamDescriptor(); + blockData.WriteToSync(writeToStream, null /* copyLength */, Constants.MaxBlockSize, requiresContentMD5, true, tempExecutionState, streamCopyState); + seekableStream.Position = startPosition; + + if (requiresContentMD5) + { + contentMD5 = streamCopyState.Md5; + } + } + + Executor.ExecuteSync( + this.PutBlockImpl(seekableStream, blockId, contentMD5, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext); + } + finally + { + if (seekableStreamCreated) { - contentMD5 = streamCopyState.Md5; + seekableStream.Dispose(); } } - - Executor.ExecuteSync( - this.PutBlockImpl(seekableStream, blockId, contentMD5, accessCondition, modifiedOptions), - modifiedOptions.RetryPolicy, - operationContext); } #endif @@ -1470,6 +1507,7 @@ public ICancellableAsyncResult BeginPutBlock(string blockId, Stream blockData, s else { seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); + storageAsyncResult.OperationState = seekableStream; writeToStream = seekableStream; } @@ -1520,7 +1558,19 @@ public ICancellableAsyncResult BeginPutBlock(string blockId, Stream blockData, s public void EndPutBlock(IAsyncResult asyncResult) { StorageAsyncResult storageAsyncResult = (StorageAsyncResult)asyncResult; - storageAsyncResult.End(); + + try + { + storageAsyncResult.End(); + } + finally + { + if (storageAsyncResult.OperationState != null) + { + MultiBufferMemoryStream stream = (MultiBufferMemoryStream)storageAsyncResult.OperationState; + stream.Dispose(); + } + } } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Reviewed.")] @@ -1656,6 +1706,7 @@ public Task PutBlockAsync(string blockId, Stream blockData, string contentMD5, A /// This method fetches the blob's ETag, last-modified time, and part of the copy state. /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. /// + [Obsolete("Deprecated this method in favor of StartCopy.")] [DoesServiceRequest] public string StartCopyFromBlob(CloudBlockBlob source, AccessCondition sourceAccessCondition = null, AccessCondition destAccessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) { @@ -1670,6 +1721,7 @@ public string StartCopyFromBlob(CloudBlockBlob source, AccessCondition sourceAcc /// An delegate that will receive notification when the asynchronous operation completes. /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. + [Obsolete("Deprecated this method in favor of BeginStartCopy.")] [DoesServiceRequest] public ICancellableAsyncResult BeginStartCopyFromBlob(CloudBlockBlob source, AsyncCallback callback, object state) { @@ -1687,6 +1739,7 @@ public ICancellableAsyncResult BeginStartCopyFromBlob(CloudBlockBlob source, Asy /// An delegate that will receive notification when the asynchronous operation completes. /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. + [Obsolete("Deprecated this method in favor of BeginStartCopy.")] [DoesServiceRequest] public ICancellableAsyncResult BeginStartCopyFromBlob(CloudBlockBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) { @@ -1699,6 +1752,7 @@ public ICancellableAsyncResult BeginStartCopyFromBlob(CloudBlockBlob source, Acc /// /// A object. /// A object of type string that represents the asynchronous operation. + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(CloudBlockBlob source) { @@ -1711,6 +1765,7 @@ public Task StartCopyFromBlobAsync(CloudBlockBlob source) /// A object. /// A to observe while waiting for a task to complete. /// A object of type string that represents the asynchronous operation. + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(CloudBlockBlob source, CancellationToken cancellationToken) { @@ -1726,6 +1781,7 @@ public Task StartCopyFromBlobAsync(CloudBlockBlob source, CancellationTo /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A object of type string that represents the asynchronous operation. + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(CloudBlockBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -1742,6 +1798,7 @@ public Task StartCopyFromBlobAsync(CloudBlockBlob source, AccessConditio /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. /// A object of type string that represents the asynchronous operation. + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(CloudBlockBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -1749,6 +1806,216 @@ public Task StartCopyFromBlobAsync(CloudBlockBlob source, AccessConditio } #endif +#if SYNC + /// + /// Begins an operation to start copying an Azure file's contents, properties, and metadata to this block blob. + /// + /// A object. + /// An object that represents the access conditions for the source file. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the blob's ETag, last-modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] + public string StartCopy(CloudFile source, AccessCondition sourceAccessCondition = null, AccessCondition destAccessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + return this.StartCopy(CloudFile.SourceFileToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext); + } + + /// + /// Begins an operation to start copying another block blob's contents, properties, and metadata to this block blob. + /// + /// A object. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the blob's ETag, last-modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] + public string StartCopy(CloudBlockBlob source, AccessCondition sourceAccessCondition = null, AccessCondition destAccessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) + { + return this.StartCopy(CloudBlob.SourceBlobToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext); + } +#endif + + /// + /// Begins an asynchronous operation to start copying a file's contents, properties, and metadata to this block blob. + /// + /// A object. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginStartCopy(CloudFile source, AsyncCallback callback, object state) + { + return this.BeginStartCopy(CloudFile.SourceFileToUri(source), callback, state); + } + + /// + /// Begins an asynchronous operation to start copying another block blob's contents, properties, and metadata to this block blob. + /// + /// A object. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginStartCopy(CloudBlockBlob source, AsyncCallback callback, object state) + { + return this.BeginStartCopy(CloudBlob.SourceBlobToUri(source), callback, state); + } + + /// + /// Begins an asynchronous operation to start copying a file's contents, properties, and metadata to this block blob. + /// + /// A object. + /// An object that represents the access conditions for the source file. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginStartCopy(CloudFile source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginStartCopy(CloudFile.SourceFileToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to start copying another block blob's contents, properties, and metadata to this block blob. + /// + /// A object. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginStartCopy(CloudBlockBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginStartCopy(CloudBlob.SourceBlobToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext, callback, state); + } + +#if TASK + /// + /// Initiates an asynchronous operation to start copying a file's contents, properties, and metadata to this block blob. + /// + /// A object. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudFile source) + { + return this.StartCopyAsync(source, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to start copying another block blob's contents, properties, and metadata to this block blob. + /// + /// A object. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudBlockBlob source) + { + return this.StartCopyAsync(source, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to start copying a file's contents, properties, and metadata to this block blob. + /// + /// A object. + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudFile source, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to start copying another block blob's contents, properties, and metadata to this block blob. + /// + /// A object. + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudBlockBlob source, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to start copying a file's contents, properties, and metadata to this block blob. + /// + /// A object. + /// An object that represents the access conditions for the source file. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudFile source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.StartCopyAsync(source, sourceAccessCondition, destAccessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to start copying another block blob's contents, properties, and metadata to this block blob. + /// + /// A object. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudBlockBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.StartCopyAsync(source, sourceAccessCondition, destAccessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to start copying a file's contents, properties, and metadata to this block blob. + /// + /// A object. + /// An object that represents the access conditions for the source file. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudFile source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to start copying another block blob's contents, properties, and metadata to this block blob. + /// + /// A object. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudBlockBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); + } +#endif + /// /// Begins an asynchronous operation to return an enumerable collection of the blob's blocks, /// using the specified block list filter. @@ -2092,116 +2359,6 @@ public Task PutBlockListAsync(IEnumerable blockList, AccessCondition acc } #endif -#if SYNC - /// - /// Begins an operation to start copying another block blob's contents, properties, and metadata to this block blob. - /// - /// A object. - /// An object that represents the access conditions for the source blob. If null, no condition is used. - /// An object that represents the access conditions for the destination blob. If null, no condition is used. - /// A object that specifies additional options for the request. If null, default options are applied to the request. - /// An object that represents the context for the current operation. - /// The copy ID associated with the copy operation. - /// - /// This method fetches the blob's ETag, last-modified time, and part of the copy state. - /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. - /// - [DoesServiceRequest] - public string StartCopy(CloudBlockBlob source, AccessCondition sourceAccessCondition = null, AccessCondition destAccessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) - { - return this.StartCopy(CloudBlob.SourceBlobToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext); - } -#endif - -#if TASK - /// - /// Initiates an asynchronous operation to start copying another block blob's contents, properties, and metadata to this block blob. - /// - /// A object. - /// A object of type string that represents the asynchronous operation. - [DoesServiceRequest] - public Task StartCopyAsync(CloudBlockBlob source) - { - return this.StartCopyAsync(source, CancellationToken.None); - } - - /// - /// Initiates an asynchronous operation to start copying another block blob's contents, properties, and metadata to this block blob. - /// - /// A object. - /// A to observe while waiting for a task to complete. - /// A object of type string that represents the asynchronous operation. - [DoesServiceRequest] - public Task StartCopyAsync(CloudBlockBlob source, CancellationToken cancellationToken) - { - return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, cancellationToken); - } - - /// - /// Initiates an asynchronous operation to start copying another block blob's contents, properties, and metadata to this block blob. - /// - /// A object. - /// An object that represents the access conditions for the source blob. If null, no condition is used. - /// An object that represents the access conditions for the destination blob. If null, no condition is used. - /// A object that specifies additional options for the request. - /// An object that represents the context for the current operation. - /// A object of type string that represents the asynchronous operation. - [DoesServiceRequest] - public Task StartCopyAsync(CloudBlockBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) - { - return this.StartCopyAsync(source, sourceAccessCondition, destAccessCondition, options, operationContext, CancellationToken.None); - } - -#endif - - /// - /// Begins an asynchronous operation to start copying another block blob's contents, properties, and metadata to this block blob. - /// - /// A object. - /// An delegate that will receive notification when the asynchronous operation completes. - /// A user-defined object that will be passed to the callback delegate. - /// An that references the asynchronous operation. - [DoesServiceRequest] - public ICancellableAsyncResult BeginStartCopy(CloudBlockBlob source, AsyncCallback callback, object state) - { - return this.BeginStartCopy(CloudBlob.SourceBlobToUri(source), callback, state); - } - - /// - /// Begins an asynchronous operation to start copying another block blob's contents, properties, and metadata to this block blob. - /// - /// A object. - /// An object that represents the access conditions for the source blob. If null, no condition is used. - /// An object that represents the access conditions for the destination blob. If null, no condition is used. - /// A object that specifies additional options for the request. - /// An object that represents the context for the current operation. - /// An delegate that will receive notification when the asynchronous operation completes. - /// A user-defined object that will be passed to the callback delegate. - /// An that references the asynchronous operation. - [DoesServiceRequest] - public ICancellableAsyncResult BeginStartCopy(CloudBlockBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) - { - return this.BeginStartCopy(CloudBlob.SourceBlobToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext, callback, state); - } - -#if TASK - /// - /// Initiates an asynchronous operation to start copying another block blob's contents, properties, and metadata to this block blob. - /// - /// A object. - /// An object that represents the access conditions for the source blob. If null, no condition is used. - /// An object that represents the access conditions for the destination blob. If null, no condition is used. - /// A object that specifies additional options for the request. - /// An object that represents the context for the current operation. - /// A to observe while waiting for a task to complete. - /// A object of type string that represents the asynchronous operation. - [DoesServiceRequest] - public Task StartCopyAsync(CloudBlockBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) - { - return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); - } -#endif - /// /// Uploads the full blob from a seekable stream. /// @@ -2247,7 +2404,7 @@ private RESTCommand PutBlobImpl(Stream stream, long? length, string co /// A that uploads the block. internal RESTCommand PutBlockImpl(Stream source, string blockId, string contentMD5, AccessCondition accessCondition, BlobRequestOptions options) { - options.AssertNoEncryptionPolicy(); + options.AssertNoEncryptionPolicyOrStrictMode(); long offset = source.Position; @@ -2307,6 +2464,7 @@ internal RESTCommand PutBlockListImpl(IEnumerable bl BlobHttpWebRequestFactory.AddMetadata(r, this.Metadata); }; putCmd.SendStream = memoryStream; + putCmd.StreamToDispose = memoryStream; putCmd.RecoveryAction = RecoveryActions.RewindStream; putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => diff --git a/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs b/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs index f008390a3..269c8a5c7 100644 --- a/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs +++ b/Lib/ClassLibraryCommon/Blob/CloudPageBlob.cs @@ -38,7 +38,7 @@ public sealed partial class CloudPageBlob : CloudBlob, ICloudBlob { #if SYNC /// - /// Opens a stream for writing to the blob. + /// Opens a stream for writing to the blob. If the blob already exists, then existing data in the blob may be overwritten. /// /// The size of the page blob, in bytes. The size must be a multiple of 512. If null, the page blob must already exist. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -49,6 +49,8 @@ public sealed partial class CloudPageBlob : CloudBlob, ICloudBlob /// Note that this method always makes a call to the method under the covers. /// Set the property before calling this method to specify the block size to write, in bytes, /// ranging from between 16 KB and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, pass in an + /// object generated using . /// [DoesServiceRequest] public CloudBlobStream OpenWrite(long? size, AccessCondition accessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) @@ -57,11 +59,16 @@ public CloudBlobStream OpenWrite(long? size, AccessCondition accessCondition = n BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, this.BlobType, this.ServiceClient, false); bool createNew = size.HasValue; +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) ICryptoTransform transform = null; - if (options != null && options.EncryptionPolicy != null) + + modifiedOptions.AssertPolicyIfRequired(); + + if (modifiedOptions.EncryptionPolicy != null) { transform = options.EncryptionPolicy.CreateAndSetEncryptionContext(this.Metadata, true /* noPadding */); } +#endif if (createNew) { @@ -74,10 +81,12 @@ public CloudBlobStream OpenWrite(long? size, AccessCondition accessCondition = n throw new ArgumentException(SR.MD5NotPossible); } +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) if (modifiedOptions.EncryptionPolicy != null) { throw new ArgumentException(SR.EncryptionNotSupportedForExistingBlobs); } +#endif this.FetchAttributes(accessCondition, options, operationContext); size = this.Properties.Length; } @@ -87,11 +96,13 @@ public CloudBlobStream OpenWrite(long? size, AccessCondition accessCondition = n accessCondition = AccessCondition.GenerateLeaseCondition(accessCondition.LeaseId); } +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) if (modifiedOptions.EncryptionPolicy != null) { return new BlobEncryptedWriteStream(this, size.Value, createNew, accessCondition, modifiedOptions, operationContext, transform); } else +#endif { return new BlobWriteStream(this, size.Value, createNew, accessCondition, modifiedOptions, operationContext); } @@ -99,7 +110,7 @@ public CloudBlobStream OpenWrite(long? size, AccessCondition accessCondition = n #endif /// - /// Begins an asynchronous operation to open a stream for writing to the blob. + /// Begins an asynchronous operation to open a stream for writing to the blob. If the blob already exists, then existing data in the blob may be overwritten. /// /// The size of the page blob, in bytes. The size must be a multiple of 512. If null, the page blob must already exist. /// An delegate that will receive notification when the asynchronous operation completes. @@ -109,6 +120,7 @@ public CloudBlobStream OpenWrite(long? size, AccessCondition accessCondition = n /// Note that this method always makes a call to the method under the covers. /// Set the property before calling this method to specify the page size to write, in multiples of 512 bytes, /// ranging from between 512 and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, see . /// [DoesServiceRequest] public ICancellableAsyncResult BeginOpenWrite(long? size, AsyncCallback callback, object state) @@ -117,7 +129,7 @@ public ICancellableAsyncResult BeginOpenWrite(long? size, AsyncCallback callback } /// - /// Begins an asynchronous operation to open a stream for writing to the blob. + /// Begins an asynchronous operation to open a stream for writing to the blob. If the blob already exists, then existing data in the blob may be overwritten. /// /// The size of the page blob, in bytes. The size must be a multiple of 512. If null, the page blob must already exist. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -130,6 +142,8 @@ public ICancellableAsyncResult BeginOpenWrite(long? size, AsyncCallback callback /// Note that this method always makes a call to the method under the covers. /// Set the property before calling this method to specify the page size to write, in multiples of 512 bytes, /// ranging from between 512 and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, pass in an + /// object generated using . /// [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Needed to ensure exceptions are not thrown on threadpool threads.")] [DoesServiceRequest] @@ -142,8 +156,11 @@ public ICancellableAsyncResult BeginOpenWrite(long? size, AccessCondition access StorageAsyncResult storageAsyncResult = new StorageAsyncResult(callback, state); ICancellableAsyncResult result; + modifiedOptions.AssertPolicyIfRequired(); + if (createNew) { +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) ICryptoTransform transform = null; if (options != null && options.EncryptionPolicy != null) { @@ -153,7 +170,7 @@ public ICancellableAsyncResult BeginOpenWrite(long? size, AccessCondition access transform = options.EncryptionPolicy.CreateAndSetEncryptionContext(this.Metadata, true /* noPadding */); #endif } - +#endif result = this.BeginCreate( size.Value, accessCondition, @@ -172,11 +189,13 @@ public ICancellableAsyncResult BeginOpenWrite(long? size, AccessCondition access accessCondition = AccessCondition.GenerateLeaseCondition(accessCondition.LeaseId); } +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) if (modifiedOptions.EncryptionPolicy != null) { storageAsyncResult.Result = new BlobEncryptedWriteStream(this, this.Properties.Length, createNew, accessCondition, modifiedOptions, operationContext, transform); } else +#endif { storageAsyncResult.Result = new BlobWriteStream(this, this.Properties.Length, createNew, accessCondition, modifiedOptions, operationContext); } @@ -197,10 +216,12 @@ public ICancellableAsyncResult BeginOpenWrite(long? size, AccessCondition access throw new ArgumentException(SR.MD5NotPossible); } +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) if (modifiedOptions.EncryptionPolicy != null) { throw new ArgumentException(SR.EncryptionNotSupportedForExistingBlobs); } +#endif result = this.BeginFetchAttributes( accessCondition, @@ -248,7 +269,7 @@ public CloudBlobStream EndOpenWrite(IAsyncResult asyncResult) #if TASK /// - /// Initiates an asynchronous operation to open a stream for writing to the blob. + /// Initiates an asynchronous operation to open a stream for writing to the blob. If the blob already exists, then existing data in the blob may be overwritten. /// /// The size of the page blob, in bytes. The size must be a multiple of 512. If null, the page blob must already exist. /// A object of type that represents the asynchronous operation. @@ -256,6 +277,7 @@ public CloudBlobStream EndOpenWrite(IAsyncResult asyncResult) /// Note that this method always makes a call to the method under the covers. /// Set the property before calling this method to specify the page size to write, in multiples of 512 bytes, /// ranging from between 512 and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, see . /// [DoesServiceRequest] public Task OpenWriteAsync(long? size) @@ -264,7 +286,7 @@ public Task OpenWriteAsync(long? size) } /// - /// Initiates an asynchronous operation to open a stream for writing to the blob. + /// Initiates an asynchronous operation to open a stream for writing to the blob. If the blob already exists, then existing data in the blob may be overwritten. /// /// The size of the page blob, in bytes. The size must be a multiple of 512. If null, the page blob must already exist. /// A to observe while waiting for a task to complete. @@ -273,6 +295,7 @@ public Task OpenWriteAsync(long? size) /// Note that this method always makes a call to the method under the covers. /// Set the property before calling this method to specify the page size to write, in multiples of 512 bytes, /// ranging from between 512 and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, see . /// [DoesServiceRequest] public Task OpenWriteAsync(long? size, CancellationToken cancellationToken) @@ -281,7 +304,7 @@ public Task OpenWriteAsync(long? size, CancellationToken cancel } /// - /// Initiates an asynchronous operation to open a stream for writing to the blob. + /// Initiates an asynchronous operation to open a stream for writing to the blob. If the blob already exists, then existing data in the blob may be overwritten. /// /// The size of the page blob, in bytes. The size must be a multiple of 512. If null, the page blob must already exist. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -292,6 +315,8 @@ public Task OpenWriteAsync(long? size, CancellationToken cancel /// Note that this method always makes a call to the method under the covers. /// Set the property before calling this method to specify the page size to write, in multiples of 512 bytes, /// ranging from between 512 and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, pass in an + /// object generated using . /// [DoesServiceRequest] public Task OpenWriteAsync(long? size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) @@ -300,7 +325,7 @@ public Task OpenWriteAsync(long? size, AccessCondition accessCo } /// - /// Initiates an asynchronous operation to open a stream for writing to the blob. + /// Initiates an asynchronous operation to open a stream for writing to the blob. If the blob already exists, then existing data in the blob may be overwritten. /// /// The size of the page blob, in bytes. The size must be a multiple of 512. If null, the page blob must already exist. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -312,6 +337,8 @@ public Task OpenWriteAsync(long? size, AccessCondition accessCo /// Note that this method always makes a call to the method under the covers. /// Set the property before calling this method to specify the page size to write, in multiples of 512 bytes, /// ranging from between 512 and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, pass in an + /// object generated using . /// [DoesServiceRequest] public Task OpenWriteAsync(long? size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) @@ -322,7 +349,7 @@ public Task OpenWriteAsync(long? size, AccessCondition accessCo #if SYNC /// - /// Uploads a stream to a page blob. + /// Uploads a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -335,7 +362,7 @@ public void UploadFromStream(Stream source, AccessCondition accessCondition = nu } /// - /// Uploads a stream to a page blob. + /// Uploads a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -349,7 +376,7 @@ public void UploadFromStream(Stream source, long length, AccessCondition accessC } /// - /// Uploads a stream to a page blob. + /// Uploads a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -395,7 +422,7 @@ internal void UploadFromStreamHelper(Stream source, long? length, AccessConditio #endif /// - /// Begins an asynchronous operation to upload a stream to a page blob. + /// Begins an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// An delegate that will receive notification when the asynchronous operation completes. @@ -408,7 +435,7 @@ public ICancellableAsyncResult BeginUploadFromStream(Stream source, AsyncCallbac } /// - /// Begins an asynchronous operation to upload a stream to a page blob. + /// Begins an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -424,7 +451,7 @@ public ICancellableAsyncResult BeginUploadFromStream(Stream source, AccessCondit } /// - /// Begins an asynchronous operation to upload a stream to a page blob. + /// Begins an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// Specifies the number of bytes from the Stream source to upload from the start position. @@ -438,7 +465,7 @@ public ICancellableAsyncResult BeginUploadFromStream(Stream source, long length, } /// - /// Begins an asynchronous operation to upload a stream to a page blob. + /// Begins an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// Specifies the number of bytes from the Stream source to upload from the start position. @@ -455,7 +482,7 @@ public ICancellableAsyncResult BeginUploadFromStream(Stream source, long length, } /// - /// Begins an asynchronous operation to upload a stream to a page blob. + /// Begins an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// Specifies the number of bytes from the Stream source to upload from the start position. @@ -584,7 +611,7 @@ public void EndUploadFromStream(IAsyncResult asyncResult) } /// - /// Initiates an asynchronous operation to upload a stream to a page blob. + /// Initiates an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// A object that represents the asynchronous operation. @@ -595,7 +622,7 @@ public Task UploadFromStreamAsync(Stream source) } /// - /// Initiates an asynchronous operation to upload a stream to a page blob. + /// Initiates an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// A to observe while waiting for a task to complete. @@ -607,7 +634,7 @@ public Task UploadFromStreamAsync(Stream source, CancellationToken cancellationT } /// - /// Initiates an asynchronous operation to upload a stream to a page blob. + /// Initiates an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -621,7 +648,7 @@ public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition } /// - /// Initiates an asynchronous operation to upload a stream to a page blob. + /// Initiates an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -636,7 +663,7 @@ public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition } /// - /// Initiates an asynchronous operation to upload a stream to a page blob. + /// Initiates an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -648,7 +675,7 @@ public Task UploadFromStreamAsync(Stream source, long length) } /// - /// Initiates an asynchronous operation to upload a stream to a page blob. + /// Initiates an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -661,7 +688,7 @@ public Task UploadFromStreamAsync(Stream source, long length, CancellationToken } /// - /// Initiates an asynchronous operation to upload a stream to a page blob. + /// Initiates an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -676,7 +703,7 @@ public Task UploadFromStreamAsync(Stream source, long length, AccessCondition ac } /// - /// Initiates an asynchronous operation to upload a stream to a page blob. + /// Initiates an asynchronous operation to upload a stream to a page blob. If the blob already exists, it will be overwritten. /// /// A object providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -693,7 +720,7 @@ public Task UploadFromStreamAsync(Stream source, long length, AccessCondition ac #if SYNC /// - /// Uploads a file to a page blob. + /// Uploads a file to a page blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -713,7 +740,7 @@ public void UploadFromFile(string path, FileMode mode, AccessCondition accessCon #endif /// - /// Begins an asynchronous operation to upload a file to a page blob. + /// Begins an asynchronous operation to upload a file to a page blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -727,7 +754,7 @@ public ICancellableAsyncResult BeginUploadFromFile(string path, FileMode mode, A } /// - /// Begins an asynchronous operation to upload a file to a page blob. + /// Begins an asynchronous operation to upload a file to a page blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -806,7 +833,7 @@ public void EndUploadFromFile(IAsyncResult asyncResult) #if TASK /// - /// Initiates an asynchronous operation to upload a file to a page blob. + /// Initiates an asynchronous operation to upload a file to a page blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -818,7 +845,7 @@ public Task UploadFromFileAsync(string path, FileMode mode) } /// - /// Initiates an asynchronous operation to upload a file to a page blob. + /// Initiates an asynchronous operation to upload a file to a page blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -831,7 +858,7 @@ public Task UploadFromFileAsync(string path, FileMode mode, CancellationToken ca } /// - /// Initiates an asynchronous operation to upload a file to a page blob. + /// Initiates an asynchronous operation to upload a file to a page blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -846,7 +873,7 @@ public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition acce } /// - /// Initiates an asynchronous operation to upload a file to a page blob. + /// Initiates an asynchronous operation to upload a file to a page blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -864,7 +891,7 @@ public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition acce #if SYNC /// - /// Uploads the contents of a byte array to a page blob. + /// Uploads the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -885,7 +912,7 @@ public void UploadFromByteArray(byte[] buffer, int index, int count, AccessCondi #endif /// - /// Begins an asynchronous operation to upload the contents of a byte array to a page blob. + /// Begins an asynchronous operation to upload the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -900,7 +927,7 @@ public ICancellableAsyncResult BeginUploadFromByteArray(byte[] buffer, int index } /// - /// Begins an asynchronous operation to upload the contents of a byte array to a page blob. + /// Begins an asynchronous operation to upload the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -921,7 +948,7 @@ public ICancellableAsyncResult BeginUploadFromByteArray(byte[] buffer, int index } /// - /// Ends an asynchronous operation to upload the contents of a byte array to a page blob. + /// Ends an asynchronous operation to upload the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. /// /// An that references the pending asynchronous operation. public void EndUploadFromByteArray(IAsyncResult asyncResult) @@ -931,7 +958,7 @@ public void EndUploadFromByteArray(IAsyncResult asyncResult) #if TASK /// - /// Initiates an asynchronous operation to upload the contents of a byte array to a page blob. + /// Initiates an asynchronous operation to upload the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -944,7 +971,7 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count) } /// - /// Initiates an asynchronous operation to upload the contents of a byte array to a page blob. + /// Initiates an asynchronous operation to upload the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -958,7 +985,7 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, Cancel } /// - /// Initiates an asynchronous operation to upload the contents of a byte array to a page blob. + /// Initiates an asynchronous operation to upload the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -974,7 +1001,7 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, Access } /// - /// Initiates an asynchronous operation to upload the contents of a byte array to a page blob. + /// Initiates an asynchronous operation to upload the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -993,7 +1020,8 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, Access #if SYNC /// - /// Creates a page blob. + /// Creates a page blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . /// /// The maximum size of the page blob, in bytes. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -1012,7 +1040,8 @@ public void Create(long size, AccessCondition accessCondition = null, BlobReques #endif /// - /// Begins an asynchronous operation to create a page blob. + /// Begins an asynchronous operation to create a page blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, + /// use . /// /// The maximum size of the page blob, in bytes. /// An delegate that will receive notification when the asynchronous operation completes. @@ -1025,7 +1054,8 @@ public ICancellableAsyncResult BeginCreate(long size, AsyncCallback callback, ob } /// - /// Begins an asynchronous operation to create a page blob. + /// Begins an asynchronous operation to create a page blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . /// /// The maximum size of the blob, in bytes. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -1057,7 +1087,8 @@ public void EndCreate(IAsyncResult asyncResult) #if TASK /// - /// Initiates an asynchronous operation to create a page blob. + /// Initiates an asynchronous operation to create a page blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, + /// use . /// /// The maximum size of the blob, in bytes. /// A object that represents the asynchronous operation. @@ -1068,7 +1099,8 @@ public Task CreateAsync(long size) } /// - /// Initiates an asynchronous operation to create a page blob. + /// Initiates an asynchronous operation to create a page blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, + /// use . /// /// The maximum size of the blob, in bytes. /// A to observe while waiting for a task to complete. @@ -1080,7 +1112,8 @@ public Task CreateAsync(long size, CancellationToken cancellationToken) } /// - /// Initiates an asynchronous operation to create a page blob. + /// Initiates an asynchronous operation to create a page blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . /// /// The maximum size of the blob, in bytes. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -1094,7 +1127,8 @@ public Task CreateAsync(long size, AccessCondition accessCondition, BlobRequestO } /// - /// Initiates an asynchronous operation to create a page blob. + /// Initiates an asynchronous operation to create a page blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . /// /// The maximum size of the blob, in bytes. /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. @@ -1616,36 +1650,49 @@ public void WritePages(Stream pageData, long startOffset, string contentMD5 = nu operationContext = operationContext ?? new OperationContext(); Stream seekableStream = pageData; - if (!pageData.CanSeek || requiresContentMD5) - { - ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions); + bool seekableStreamCreated = false; - Stream writeToStream; - if (pageData.CanSeek) - { - writeToStream = Stream.Null; - } - else + try + { + if (!pageData.CanSeek || requiresContentMD5) { - seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); - writeToStream = seekableStream; - } + ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions); - long startPosition = seekableStream.Position; - StreamDescriptor streamCopyState = new StreamDescriptor(); - pageData.WriteToSync(writeToStream, null /* copyLength */, Constants.MaxBlockSize, requiresContentMD5, true, tempExecutionState, streamCopyState); - seekableStream.Position = startPosition; + Stream writeToStream; + if (pageData.CanSeek) + { + writeToStream = Stream.Null; + } + else + { + seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); + seekableStreamCreated = true; + writeToStream = seekableStream; + } + + long startPosition = seekableStream.Position; + StreamDescriptor streamCopyState = new StreamDescriptor(); + pageData.WriteToSync(writeToStream, null /* copyLength */, Constants.MaxBlockSize, requiresContentMD5, true, tempExecutionState, streamCopyState); + seekableStream.Position = startPosition; - if (requiresContentMD5) + if (requiresContentMD5) + { + contentMD5 = streamCopyState.Md5; + } + } + + Executor.ExecuteSync( + this.PutPageImpl(seekableStream, startOffset, contentMD5, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext); + } + finally + { + if (seekableStreamCreated) { - contentMD5 = streamCopyState.Md5; + seekableStream.Dispose(); } } - - Executor.ExecuteSync( - this.PutPageImpl(seekableStream, startOffset, contentMD5, accessCondition, modifiedOptions), - modifiedOptions.RetryPolicy, - operationContext); } #endif @@ -1718,6 +1765,7 @@ public ICancellableAsyncResult BeginWritePages(Stream pageData, long startOffset else { seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); + storageAsyncResult.OperationState = seekableStream; writeToStream = seekableStream; } @@ -1801,7 +1849,19 @@ private void WritePagesHandler(Stream pageData, long startOffset, string content public void EndWritePages(IAsyncResult asyncResult) { StorageAsyncResult storageAsyncResult = (StorageAsyncResult)asyncResult; - storageAsyncResult.End(); + + try + { + storageAsyncResult.End(); + } + finally + { + if (storageAsyncResult.OperationState != null) + { + MultiBufferMemoryStream stream = (MultiBufferMemoryStream)storageAsyncResult.OperationState; + stream.Dispose(); + } + } } #if TASK @@ -2028,6 +2088,7 @@ public Task ClearPagesAsync(long startOffset, long length, AccessCondition acces /// This method fetches the blob's ETag, last-modified time, and part of the copy state. /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. /// + [Obsolete("Deprecated this method in favor of StartCopy.")] [DoesServiceRequest] public string StartCopyFromBlob(CloudPageBlob source, AccessCondition sourceAccessCondition = null, AccessCondition destAccessCondition = null, BlobRequestOptions options = null, OperationContext operationContext = null) { @@ -2042,6 +2103,7 @@ public string StartCopyFromBlob(CloudPageBlob source, AccessCondition sourceAcce /// An delegate that will receive notification when the asynchronous operation completes. /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. + [Obsolete("Deprecated this method in favor of BeginStartCopy.")] [DoesServiceRequest] public ICancellableAsyncResult BeginStartCopyFromBlob(CloudPageBlob source, AsyncCallback callback, object state) { @@ -2059,6 +2121,7 @@ public ICancellableAsyncResult BeginStartCopyFromBlob(CloudPageBlob source, Asyn /// An delegate that will receive notification when the asynchronous operation completes. /// A user-defined object that will be passed to the callback delegate. /// An that references the asynchronous operation. + [Obsolete("Deprecated this method in favor of BeginStartCopy.")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest", Justification = "Reviewed")] [DoesServiceRequest] public ICancellableAsyncResult BeginStartCopyFromBlob(CloudPageBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) @@ -2074,6 +2137,7 @@ public ICancellableAsyncResult BeginStartCopyFromBlob(CloudPageBlob source, Acce /// /// The that is the source blob. /// A object of type string that represents the asynchronous operation. + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(CloudPageBlob source) { @@ -2087,6 +2151,7 @@ public Task StartCopyFromBlobAsync(CloudPageBlob source) /// The that is the source blob. /// A to observe while waiting for a task to complete. /// A object of type string that represents the asynchronous operation. + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(CloudPageBlob source, CancellationToken cancellationToken) { @@ -2103,6 +2168,7 @@ public Task StartCopyFromBlobAsync(CloudPageBlob source, CancellationTok /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A object of type string that represents the asynchronous operation. + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(CloudPageBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -2120,6 +2186,7 @@ public Task StartCopyFromBlobAsync(CloudPageBlob source, AccessCondition /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. /// A object of type string that represents the asynchronous operation. + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(CloudPageBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -2356,7 +2423,7 @@ private RESTCommand> GetPageRangesImpl(long? offset, long /// A that writes the pages. private RESTCommand PutPageImpl(Stream pageData, long startOffset, string contentMD5, AccessCondition accessCondition, BlobRequestOptions options) { - options.AssertNoEncryptionPolicy(); + options.AssertNoEncryptionPolicyOrStrictMode(); if (startOffset % Constants.PageSize != 0) { @@ -2410,7 +2477,7 @@ private RESTCommand PutPageImpl(Stream pageData, long startOffset, str private RESTCommand ClearPageImpl(long startOffset, long length, AccessCondition accessCondition, BlobRequestOptions options) { CommonUtility.AssertNotNull("options", options); - options.AssertNoEncryptionPolicy(); + options.AssertNoEncryptionPolicyOrStrictMode(); if (startOffset < 0 || startOffset % Constants.PageSize != 0) { diff --git a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs index e2699bbb8..ee552ff01 100644 --- a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpResponseParsers.cs @@ -110,6 +110,13 @@ public static BlobProperties GetProperties(HttpWebResponse response) properties.PageBlobSequenceNumber = long.Parse(sequenceNumber, CultureInfo.InvariantCulture); } + // Get committed block count + string comittedBlockCount = response.Headers[Constants.HeaderConstants.BlobCommittedBlockCount]; + if (!string.IsNullOrEmpty(comittedBlockCount)) + { + properties.AppendBlobCommittedBlockCount = int.Parse(comittedBlockCount, CultureInfo.InvariantCulture); + } + return properties; } diff --git a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs index 9c330564a..2f4f05c73 100644 --- a/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/Blob/Protocol/BlobHttpWebRequestFactory.cs @@ -27,7 +27,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob.Protocol using System.Net; /// - /// A factory class for constructing a web request to manage blobs in the Blob service. + /// A factory class for constructing HTTP web requests for the Blob service. /// public static class BlobHttpWebRequestFactory { @@ -50,7 +50,7 @@ public static HttpWebRequest GetServiceProperties(Uri uri, UriQueryBuilder build /// A specifying the Blob service endpoint. /// A object specifying additional parameters to add to the URI query string. /// The server timeout interval, in seconds. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. internal static HttpWebRequest GetServiceProperties(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -77,7 +77,7 @@ public static HttpWebRequest SetServiceProperties(Uri uri, UriQueryBuilder build /// A specifying the Blob service endpoint. /// A object specifying additional parameters to add to the URI query string. /// The server timeout interval, in seconds. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. internal static HttpWebRequest SetServiceProperties(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -104,7 +104,7 @@ public static HttpWebRequest GetServiceStats(Uri uri, UriQueryBuilder builder, i /// A specifying the Blob service endpoint. /// A object specifying additional parameters to add to the URI query string. /// The server timeout interval, in seconds. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. internal static HttpWebRequest GetServiceStats(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -153,7 +153,7 @@ public static HttpWebRequest Put(Uri uri, int? timeout, BlobProperties propertie /// For a page blob, the size of the blob. This parameter is ignored /// for block blobs. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest Put(Uri uri, int? timeout, BlobProperties properties, BlobType blobType, long pageBlobSize, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -204,10 +204,14 @@ public static HttpWebRequest Put(Uri uri, int? timeout, BlobProperties propertie request.Headers[Constants.HeaderConstants.BlobContentLengthHeader] = pageBlobSize.ToString(NumberFormatInfo.InvariantInfo); properties.Length = pageBlobSize; } - else + else if (blobType == BlobType.BlockBlob) { request.Headers[Constants.HeaderConstants.BlobType] = Constants.HeaderConstants.BlockBlob; } + else + { + request.Headers[Constants.HeaderConstants.BlobType] = Constants.HeaderConstants.AppendBlob; + } request.ApplyAccessCondition(accessCondition); @@ -223,10 +227,43 @@ private static void AddSnapshot(UriQueryBuilder builder, DateTimeOffset? snapsho { if (snapshot.HasValue) { - builder.Add("snapshot", BlobRequest.ConvertDateTimeToSnapshotString(snapshot.Value)); + builder.Add("snapshot", Request.ConvertDateTimeToSnapshotString(snapshot.Value)); } } + /// + /// Constructs a web request to commit a block to an append blob. + /// + /// A specifying the absolute URI to the blob. + /// An integer specifying the server timeout interval. + /// An object that represents the condition that must be met in order for the request to proceed. + /// An object that represents the context for the current operation. + /// A object. + public static HttpWebRequest AppendBlock(Uri uri, int? timeout, AccessCondition accessCondition, OperationContext operationContext) + { + return BlobHttpWebRequestFactory.AppendBlock(uri, timeout, accessCondition, true /* useVersionHeader */, operationContext); + } + + /// + /// Constructs a web request to commit a block to an append blob. + /// + /// A specifying the absolute URI to the blob. + /// An integer specifying the server timeout interval. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A boolean value indicating whether to set the x-ms-version HTTP header. + /// An object that represents the context for the current operation. + /// A object. + public static HttpWebRequest AppendBlock(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + { + UriQueryBuilder builder = new UriQueryBuilder(); + builder.Add(Constants.QueryConstants.Component, "appendblock"); + + HttpWebRequest request = HttpWebRequestFactory.CreateWebRequest(WebRequestMethods.Http.Put, uri, timeout, builder, useVersionHeader, operationContext); + request.ApplyAccessCondition(accessCondition); + request.ApplyAppendCondition(accessCondition); + return request; + } + /// /// Constructs a web request to return the list of valid page ranges for a page blob. /// @@ -252,7 +289,7 @@ public static HttpWebRequest GetPageRanges(Uri uri, int? timeout, DateTimeOffset /// The starting offset of the data range over which to list page ranges, in bytes. Must be a multiple of 512. /// The length of the data range over which to list page ranges, in bytes. Must be a multiple of 512. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest GetPageRanges(Uri uri, int? timeout, DateTimeOffset? snapshot, long? offset, long? count, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -321,7 +358,7 @@ public static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset /// An integer specifying the server timeout interval. /// A specifying the snapshot timestamp, if the blob is a snapshot. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest GetProperties(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -355,7 +392,7 @@ public static HttpWebRequest SetProperties(Uri uri, int? timeout, BlobProperties /// An integer specifying the server timeout interval. /// The blob's properties. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest SetProperties(Uri uri, int? timeout, BlobProperties properties, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -402,7 +439,7 @@ public static HttpWebRequest Resize(Uri uri, int? timeout, long newBlobSize, Acc /// An integer specifying the server timeout interval. /// The new blob size, if the blob is a page blob. Set this parameter to null to keep the existing blob size. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest Resize(Uri uri, int? timeout, long newBlobSize, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -441,7 +478,7 @@ public static HttpWebRequest SetSequenceNumber(Uri uri, int? timeout, SequenceNu /// A value of type , indicating the operation to perform on the sequence number. /// The sequence number. Set this parameter to null if this operation is an increment action. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest SetSequenceNumber(Uri uri, int? timeout, SequenceNumberAction sequenceNumberAction, long? sequenceNumber, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -497,7 +534,7 @@ public static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? /// An integer specifying the server timeout interval. /// A specifying the snapshot timestamp, if the blob is a snapshot. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest GetMetadata(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -529,7 +566,7 @@ public static HttpWebRequest SetMetadata(Uri uri, int? timeout, AccessCondition /// A specifying the absolute URI to the blob. /// An integer specifying the server timeout interval. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest SetMetadata(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -583,7 +620,7 @@ public static HttpWebRequest Delete(Uri uri, int? timeout, DateTimeOffset? snaps /// A specifying the snapshot timestamp, if the blob is a snapshot. /// A set of options indicating whether to delete only blobs, only snapshots, or both. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest Delete(Uri uri, int? timeout, DateTimeOffset? snapshot, DeleteSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -639,7 +676,7 @@ public static HttpWebRequest Snapshot(Uri uri, int? timeout, AccessCondition acc /// A specifying the absolute URI to the blob. /// An integer specifying the server timeout interval. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest Snapshot(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -685,7 +722,7 @@ public static HttpWebRequest Lease(Uri uri, int? timeout, LeaseAction action, st /// The amount of time to wait, in seconds, after a break operation before the lease is broken. /// If this is null then the default time is used. This should be null for acquire, renew, change, and release operations. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest Lease(Uri uri, int? timeout, LeaseAction action, string proposedLeaseId, int? leaseDuration, int? leaseBreakPeriod, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -766,7 +803,7 @@ public static HttpWebRequest PutBlock(Uri uri, int? timeout, string blockId, Acc /// An integer specifying the server timeout interval. /// A string specifying the block ID for this block. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest PutBlock(Uri uri, int? timeout, string blockId, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -801,7 +838,7 @@ public static HttpWebRequest PutBlockList(Uri uri, int? timeout, BlobProperties /// An integer specifying the server timeout interval. /// A object specifying the properties to set for the blob. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest PutBlockList(Uri uri, int? timeout, BlobProperties properties, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -850,7 +887,7 @@ public static HttpWebRequest GetBlockList(Uri uri, int? timeout, DateTimeOffset? /// A specifying the snapshot timestamp, if the blob is a snapshot. /// A enumeration value. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest GetBlockList(Uri uri, int? timeout, DateTimeOffset? snapshot, BlockListingFilter typesOfBlocks, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -874,9 +911,7 @@ public static HttpWebRequest GetBlockList(Uri uri, int? timeout, DateTimeOffset? /// A enumeration value indicating the operation to perform on the page blob. /// An object that represents the condition that must be met in order for the request to proceed. /// An object that represents the context for the current operation. - /// - /// A web request to use to perform the operation. - /// + /// A object. public static HttpWebRequest PutPage(Uri uri, int? timeout, PageRange pageRange, PageWrite pageWrite, AccessCondition accessCondition, OperationContext operationContext) { return BlobHttpWebRequestFactory.PutPage(uri, timeout, pageRange, pageWrite, accessCondition, true /* useVersionHeader */, operationContext); @@ -890,7 +925,7 @@ public static HttpWebRequest PutPage(Uri uri, int? timeout, PageRange pageRange, /// A object. /// A enumeration value indicating the operation to perform on the page blob. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// /// A web request to use to perform the operation. @@ -913,12 +948,12 @@ public static HttpWebRequest PutPage(Uri uri, int? timeout, PageRange pageRange, } /// - /// Generates a web request to copy a blob. + /// Generates a web request to copy a blob or file to another blob. /// /// A specifying the absolute URI to the destination blob. /// An integer specifying the server timeout interval. - /// A specifying the absolute URI to the source blob, including any necessary authentication parameters. - /// An object that represents the condition that must be met on the source blob in order for the request to proceed. + /// A specifying the absolute URI to the source object, including any necessary authentication parameters. + /// An object that represents the condition that must be met on the source object in order for the request to proceed. /// An object that represents the condition that must be met on the destination blob in order for the request to proceed. /// An object that represents the context for the current operation. /// A object. @@ -928,14 +963,14 @@ public static HttpWebRequest CopyFrom(Uri uri, int? timeout, Uri source, AccessC } /// - /// Generates a web request to copy a blob. + /// Generates a web request to copy a blob or file to another blob. /// /// A specifying the absolute URI to the destination blob. /// An integer specifying the server timeout interval. - /// A specifying the absolute URI to the source blob, including any necessary authentication parameters. - /// An object that represents the condition that must be met on the source blob in order for the request to proceed. + /// A specifying the absolute URI to the source object, including any necessary authentication parameters. + /// An object that represents the condition that must be met on the source object in order for the request to proceed. /// An object that represents the condition that must be met on the destination blob in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest CopyFrom(Uri uri, int? timeout, Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, bool useVersionHeader, OperationContext operationContext) @@ -972,7 +1007,7 @@ public static HttpWebRequest AbortCopy(Uri uri, int? timeout, string copyId, Acc /// An integer specifying the server timeout interval. /// The ID string of the copy operation to be aborted. /// An object that represents the condition that must be met in order for the request to proceed. Only lease conditions are supported for this operation. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest AbortCopy(Uri uri, int? timeout, string copyId, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -1010,7 +1045,7 @@ public static HttpWebRequest Get(Uri uri, int? timeout, DateTimeOffset? snapshot /// An integer specifying the server timeout interval. /// A specifying the snapshot version, if the blob is a snapshot. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest Get(Uri uri, int? timeout, DateTimeOffset? snapshot, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -1018,7 +1053,7 @@ public static HttpWebRequest Get(Uri uri, int? timeout, DateTimeOffset? snapshot UriQueryBuilder builder = new UriQueryBuilder(); if (snapshot.HasValue) { - builder.Add("snapshot", BlobRequest.ConvertDateTimeToSnapshotString(snapshot.Value)); + builder.Add("snapshot", Request.ConvertDateTimeToSnapshotString(snapshot.Value)); } HttpWebRequest request = HttpWebRequestFactory.CreateWebRequest(WebRequestMethods.Http.Get, uri, timeout, builder, useVersionHeader, operationContext); @@ -1055,7 +1090,7 @@ public static HttpWebRequest Get(Uri uri, int? timeout, DateTimeOffset? snapshot /// The number of bytes to return, or null to return all bytes through the end of the blob. /// If set to true, an MD5 header is requested for the specified range. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// /// A web request to use to perform the operation. diff --git a/Lib/ClassLibraryCommon/Blob/Protocol/ContainerHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/Blob/Protocol/ContainerHttpWebRequestFactory.cs index fc25e5fcc..034e4d647 100644 --- a/Lib/ClassLibraryCommon/Blob/Protocol/ContainerHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/Blob/Protocol/ContainerHttpWebRequestFactory.cs @@ -48,7 +48,7 @@ public static HttpWebRequest Create(Uri uri, int? timeout, OperationContext oper /// /// A specifying the absolute URI to the container. /// An integer specifying the server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest Create(Uri uri, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -74,7 +74,7 @@ public static HttpWebRequest Create(Uri uri, int? timeout, OperationContext oper /// /// A specifying the absolute URI to the container. /// An integer specifying the server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// An object that specifies whether data in the container may be accessed publicly and the level of access. /// A object. @@ -110,7 +110,7 @@ public static HttpWebRequest Delete(Uri uri, int? timeout, AccessCondition acces /// A specifying the absolute URI to the container. /// An integer specifying the server timeout interval. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest Delete(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -140,7 +140,7 @@ public static HttpWebRequest GetMetadata(Uri uri, int? timeout, AccessCondition /// A specifying the absolute URI to the container. /// An integer specifying the server timeout interval. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest GetMetadata(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -170,7 +170,7 @@ public static HttpWebRequest GetProperties(Uri uri, int? timeout, AccessConditio /// A specifying the absolute URI to the container. /// An integer specifying the server timeout interval. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest GetProperties(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -200,7 +200,7 @@ public static HttpWebRequest SetMetadata(Uri uri, int? timeout, AccessCondition /// A specifying the absolute URI to the container. /// An integer specifying the server timeout interval. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest SetMetadata(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -244,7 +244,7 @@ public static HttpWebRequest Lease(Uri uri, int? timeout, LeaseAction action, st /// The amount of time to wait, in seconds, after a break operation before the lease is broken. /// If this is null then the default time is used. This should be null for acquire, renew, change, and release operations. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest Lease(Uri uri, int? timeout, LeaseAction action, string proposedLeaseId, int? leaseDuration, int? leaseBreakPeriod, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -306,7 +306,7 @@ public static HttpWebRequest List(Uri uri, int? timeout, ListingContext listingC /// An integer specifying the server timeout interval. /// A object. /// A enumeration value that indicates whether to return container metadata with the listing. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A web request for the specified operation. public static HttpWebRequest List(Uri uri, int? timeout, ListingContext listingContext, ContainerListingDetails detailsIncluded, bool useVersionHeader, OperationContext operationContext) @@ -360,7 +360,7 @@ public static HttpWebRequest GetAcl(Uri uri, int? timeout, AccessCondition acces /// A specifying the absolute URI to the container. /// An integer specifying the server timeout interval. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest GetAcl(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -391,7 +391,7 @@ public static HttpWebRequest SetAcl(Uri uri, int? timeout, BlobContainerPublicAc /// An integer specifying the server timeout interval. /// The type of public access to allow for the container. /// An object that represents the condition that must be met in order for the request to proceed. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest SetAcl(Uri uri, int? timeout, BlobContainerPublicAccessType publicAccess, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -426,7 +426,7 @@ public static HttpWebRequest ListBlobs(Uri uri, int? timeout, BlobListingContext /// A specifying the absolute URI to the container. /// An integer specifying the server timeout interval. /// A object. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest ListBlobs(Uri uri, int? timeout, BlobListingContext listingContext, bool useVersionHeader, OperationContext operationContext) diff --git a/Lib/ClassLibraryCommon/Core/BlobDecryptStream.cs b/Lib/ClassLibraryCommon/Core/BlobDecryptStream.cs index 800d94195..0da93c4ee 100644 --- a/Lib/ClassLibraryCommon/Core/BlobDecryptStream.cs +++ b/Lib/ClassLibraryCommon/Core/BlobDecryptStream.cs @@ -41,9 +41,10 @@ internal class BlobDecryptStream : Stream private bool bufferIV; private bool noPadding; private bool disposed; + private bool? requireEncryption; private ICryptoTransform transform; - public BlobDecryptStream(Stream userStream, IDictionary metadata, long? userProvidedLength, int discardFirst, bool bufferIV, bool noPadding, BlobEncryptionPolicy policy) + public BlobDecryptStream(Stream userStream, IDictionary metadata, long? userProvidedLength, int discardFirst, bool bufferIV, bool noPadding, BlobEncryptionPolicy policy, bool? requireEncryption) { this.userStream = userStream; this.metadata = metadata; @@ -52,6 +53,7 @@ public BlobDecryptStream(Stream userStream, IDictionary metadata this.encryptionPolicy = policy; this.bufferIV = bufferIV; this.noPadding = noPadding; + this.requireEncryption = requireEncryption; } public override bool CanRead @@ -137,7 +139,7 @@ public override void Write(byte[] buffer, int offset, int count) if (this.cryptoStream == null) { LengthLimitingStream lengthLimitingStream = new LengthLimitingStream(this.userStream, this.discardFirst, this.userProvidedLength); - this.cryptoStream = this.encryptionPolicy.DecryptBlob(lengthLimitingStream, this.metadata, out this.transform, !this.bufferIV ? null : this.iv, this.noPadding); + this.cryptoStream = this.encryptionPolicy.DecryptBlob(lengthLimitingStream, this.metadata, out this.transform, this.requireEncryption, iv: !this.bufferIV ? null : this.iv, noPadding: this.noPadding); } // Route the remaining data through the crypto stream. @@ -168,7 +170,7 @@ public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, As if (this.cryptoStream == null) { LengthLimitingStream lengthLimitingStream = new LengthLimitingStream(this.userStream, this.discardFirst, this.userProvidedLength); - this.cryptoStream = this.encryptionPolicy.DecryptBlob(lengthLimitingStream, this.metadata, out this.transform, !this.bufferIV ? null : this.iv, this.noPadding); + this.cryptoStream = this.encryptionPolicy.DecryptBlob(lengthLimitingStream, this.metadata, out this.transform, this.requireEncryption, iv: !this.bufferIV ? null : this.iv, noPadding: this.noPadding); } StorageAsyncResult storageAsyncResult = new StorageAsyncResult(callback, state); diff --git a/Lib/ClassLibraryCommon/Core/LengthLimitingStream.cs b/Lib/ClassLibraryCommon/Core/LengthLimitingStream.cs index 1aa9fea40..947294942 100644 --- a/Lib/ClassLibraryCommon/Core/LengthLimitingStream.cs +++ b/Lib/ClassLibraryCommon/Core/LengthLimitingStream.cs @@ -135,7 +135,6 @@ public override int Read(byte[] buffer, int offset, int count) return this.wrappedStream.Read(buffer, offset, count); } - #if WINDOWS_DESKTOP public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { diff --git a/Lib/ClassLibraryCommon/File/CloudFile.cs b/Lib/ClassLibraryCommon/File/CloudFile.cs index 63d59b43f..3a44b1a1c 100644 --- a/Lib/ClassLibraryCommon/File/CloudFile.cs +++ b/Lib/ClassLibraryCommon/File/CloudFile.cs @@ -17,6 +17,7 @@ namespace Microsoft.WindowsAzure.Storage.File { + using Microsoft.WindowsAzure.Storage.Blob; using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Executor; using Microsoft.WindowsAzure.Storage.Core.Util; @@ -175,13 +176,13 @@ public Task OpenReadAsync(AccessCondition accessCondition, FileRequestOp #if SYNC /// - /// Opens a stream for writing to the file. + /// Opens a stream for writing to the file. If the file already exists, then existing data in the file may be overwritten. /// /// The size of the file, in bytes. If null, the file must already exist. /// An object that represents the access conditions for the file. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. - /// A stream to be used for writing to the file. + /// A object to be used for writing to the file. [DoesServiceRequest] public CloudFileStream OpenWrite(long? size, AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) { @@ -214,7 +215,7 @@ public CloudFileStream OpenWrite(long? size, AccessCondition accessCondition = n #endif /// - /// Begins an asynchronous operation to open a stream for writing to the file. + /// Begins an asynchronous operation to open a stream for writing to the file. If the file already exists, then existing data in the file may be overwritten. /// /// The size of the file, in bytes. If null, the file must already exist. /// The callback delegate that will receive notification when the asynchronous operation completes. @@ -227,7 +228,7 @@ public ICancellableAsyncResult BeginOpenWrite(long? size, AsyncCallback callback } /// - /// Begins an asynchronous operation to open a stream for writing to the file. + /// Begins an asynchronous operation to open a stream for writing to the file. If the file already exists, then existing data in the file may be overwritten. /// /// The size of the file, in bytes. If null, the file must already exist. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -319,7 +320,7 @@ public ICancellableAsyncResult BeginOpenWrite(long? size, AccessCondition access /// Ends an asynchronous operation to open a stream for writing to the file. /// /// An that references the pending asynchronous operation. - /// A stream to be used for writing to the file. + /// A object to be used for writing to the file. public CloudFileStream EndOpenWrite(IAsyncResult asyncResult) { StorageAsyncResult storageAsyncResult = (StorageAsyncResult)asyncResult; @@ -329,7 +330,7 @@ public CloudFileStream EndOpenWrite(IAsyncResult asyncResult) #if TASK /// - /// Returns a task that performs an asynchronous operation to open a stream for writing to the file. + /// Returns a task that performs an asynchronous operation to open a stream for writing to the file. If the file already exists, then existing data in the file may be overwritten. /// /// The size of the file, in bytes. If null, the file must already exist. /// A object that represents the current operation. @@ -340,7 +341,7 @@ public Task OpenWriteAsync(long? size) } /// - /// Returns a task that performs an asynchronous operation to open a stream for writing to the file. + /// Returns a task that performs an asynchronous operation to open a stream for writing to the file. If the file already exists, then existing data in the file may be overwritten. /// /// The size of the file, in bytes. If null, the file must already exist. /// A to observe while waiting for a task to complete. @@ -352,7 +353,7 @@ public Task OpenWriteAsync(long? size, CancellationToken cancel } /// - /// Returns a task that performs an asynchronous operation to open a stream for writing to the file. + /// Returns a task that performs an asynchronous operation to open a stream for writing to the file. If the file already exists, then existing data in the file may be overwritten. /// /// The size of the file, in bytes. If null, the file must already exist. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -366,7 +367,7 @@ public Task OpenWriteAsync(long? size, AccessCondition accessCo } /// - /// Returns a task that performs an asynchronous operation to open a stream for writing to the file. + /// Returns a task that performs an asynchronous operation to open a stream for writing to the file. If the file already exists, then existing data in the file may be overwritten. /// /// The size of the file, in bytes. If null, the file must already exist. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -1268,7 +1269,7 @@ public Task DownloadRangeToByteArrayAsync(byte[] target, int index, long? f #if SYNC /// - /// Uploads a stream to a file. + /// Uploads a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -1281,7 +1282,7 @@ public void UploadFromStream(Stream source, AccessCondition accessCondition = nu } /// - /// Uploads a stream to a file. + /// Uploads a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// The number of bytes to write from the source stream at its current position. @@ -1295,7 +1296,7 @@ public void UploadFromStream(Stream source, long length, AccessCondition accessC } /// - /// Uploads a stream to a file. + /// Uploads a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// The number of bytes to write from the source stream at its current position. @@ -1335,7 +1336,7 @@ internal void UploadFromStreamHelper(Stream source, long? length, AccessConditio #endif /// - /// Begins an asynchronous operation to upload a stream to a file. + /// Begins an asynchronous operation to upload a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// The callback delegate that will receive notification when the asynchronous operation completes. @@ -1348,7 +1349,7 @@ public ICancellableAsyncResult BeginUploadFromStream(Stream source, AsyncCallbac } /// - /// Begins an asynchronous operation to upload a stream to a file. + /// Begins an asynchronous operation to upload a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -1364,7 +1365,7 @@ public ICancellableAsyncResult BeginUploadFromStream(Stream source, AccessCondit } /// - /// Begins an asynchronous operation to upload a stream to a file. + /// Begins an asynchronous operation to upload a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// Specifies the number of bytes from the Stream source to upload from the start position. @@ -1378,7 +1379,7 @@ public ICancellableAsyncResult BeginUploadFromStream(Stream source, long length, } /// - /// Begins an asynchronous operation to upload a stream to a file. + /// Begins an asynchronous operation to upload a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// Specifies the number of bytes from the Stream source to upload from the start position. @@ -1395,7 +1396,7 @@ public ICancellableAsyncResult BeginUploadFromStream(Stream source, long length, } /// - /// Begins an asynchronous operation to upload a stream to a file. + /// Begins an asynchronous operation to upload a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// Specifies the number of bytes from the Stream source to upload from the start position. @@ -1519,7 +1520,7 @@ public void EndUploadFromStream(IAsyncResult asyncResult) #if TASK /// - /// Returns a task that performs an asynchronous operation to upload a stream to a file. + /// Returns a task that performs an asynchronous operation to upload a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// A object that represents the current operation. @@ -1530,7 +1531,7 @@ public Task UploadFromStreamAsync(Stream source) } /// - /// Returns a task that performs an asynchronous operation to upload a stream to a file. + /// Returns a task that performs an asynchronous operation to upload a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// A to observe while waiting for a task to complete. @@ -1542,7 +1543,7 @@ public Task UploadFromStreamAsync(Stream source, CancellationToken cancellationT } /// - /// Returns a task that performs an asynchronous operation to upload a stream to a file. + /// Returns a task that performs an asynchronous operation to upload a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -1556,7 +1557,7 @@ public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition } /// - /// Returns a task that performs an asynchronous operation to upload a stream to a file. + /// Returns a task that performs an asynchronous operation to upload a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -1571,7 +1572,7 @@ public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition } /// - /// Returns a task that performs an asynchronous operation to upload a stream to a file. + /// Returns a task that performs an asynchronous operation to upload a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// The number of bytes to write from the source stream at its current position. @@ -1583,7 +1584,7 @@ public Task UploadFromStreamAsync(Stream source, long length) } /// - /// Returns a task that performs an asynchronous operation to upload a stream to a file. + /// Returns a task that performs an asynchronous operation to upload a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// The number of bytes to write from the source stream at its current position. @@ -1596,7 +1597,7 @@ public Task UploadFromStreamAsync(Stream source, long length, CancellationToken } /// - /// Returns a task that performs an asynchronous operation to upload a stream to a file. + /// Returns a task that performs an asynchronous operation to upload a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// The number of bytes to write from the source stream at its current position. @@ -1611,7 +1612,7 @@ public Task UploadFromStreamAsync(Stream source, long length, AccessCondition ac } /// - /// Returns a task that performs an asynchronous operation to upload a stream to a file. + /// Returns a task that performs an asynchronous operation to upload a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// The number of bytes to write from the source stream at its current position. @@ -1629,7 +1630,7 @@ public Task UploadFromStreamAsync(Stream source, long length, AccessCondition ac #if SYNC /// - /// Uploads a file to the File service. + /// Uploads a file to the File service. If the file already exists on the service, it will be overwritten. /// /// The file providing the content. /// A constant that determines how to open the file. @@ -1649,7 +1650,7 @@ public void UploadFromFile(string path, FileMode mode, AccessCondition accessCon #endif /// - /// Begins an asynchronous operation to upload a file to the File service. + /// Begins an asynchronous operation to upload a file to the File service. If the file already exists on the service, it will be overwritten. /// /// The file providing the content. /// A constant that determines how to open the file. @@ -1663,7 +1664,7 @@ public ICancellableAsyncResult BeginUploadFromFile(string path, FileMode mode, A } /// - /// Begins an asynchronous operation to upload a file to the File service. + /// Begins an asynchronous operation to upload a file to the File service. If the file already exists on the service, it will be overwritten. /// /// The file providing the content. /// A constant that determines how to open the file. @@ -1742,7 +1743,7 @@ public void EndUploadFromFile(IAsyncResult asyncResult) #if TASK /// - /// Returns a task that performs an asynchronous operation to upload a local file to the File service. + /// Returns a task that performs an asynchronous operation to upload a local file to the File service. If the file already exists on the service, it will be overwritten. /// /// The file providing the file content. /// A constant that determines how to open the file. @@ -1754,7 +1755,7 @@ public Task UploadFromFileAsync(string path, FileMode mode) } /// - /// Returns a task that performs an asynchronous operation to upload a local file to the File service. + /// Returns a task that performs an asynchronous operation to upload a local file to the File service. If the file already exists on the service, it will be overwritten. /// /// The file providing the file content. /// A constant that determines how to open the file. @@ -1767,7 +1768,7 @@ public Task UploadFromFileAsync(string path, FileMode mode, CancellationToken ca } /// - /// Returns a task that performs an asynchronous operation to upload a local file to the File service. + /// Returns a task that performs an asynchronous operation to upload a local file to the File service. If the file already exists on the service, it will be overwritten. /// /// The file providing the file content. /// A constant that determines how to open the file. @@ -1782,7 +1783,7 @@ public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition acce } /// - /// Returns a task that performs an asynchronous operation to upload a local file to the File service. + /// Returns a task that performs an asynchronous operation to upload a local file to the File service. If the file already exists on the service, it will be overwritten. /// /// The file providing the file content. /// A constant that determines how to open the file. @@ -1800,7 +1801,7 @@ public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition acce #if SYNC /// - /// Uploads the contents of a byte array to a file. + /// Uploads the contents of a byte array to a file. If the file already exists on the service, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the file. @@ -1821,7 +1822,7 @@ public void UploadFromByteArray(byte[] buffer, int index, int count, AccessCondi #endif /// - /// Begins an asynchronous operation to upload the contents of a byte array to a file. + /// Begins an asynchronous operation to upload the contents of a byte array to a file. If the file already exists on the service, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the file. @@ -1836,7 +1837,7 @@ public ICancellableAsyncResult BeginUploadFromByteArray(byte[] buffer, int index } /// - /// Begins an asynchronous operation to upload the contents of a byte array to a file. + /// Begins an asynchronous operation to upload the contents of a byte array to a file. If the file already exists on the service, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the file. @@ -1867,7 +1868,7 @@ public void EndUploadFromByteArray(IAsyncResult asyncResult) #if TASK /// - /// Returns a task that performs an asynchronous operation to upload the contents of a byte array to a file. + /// Returns a task that performs an asynchronous operation to upload the contents of a byte array to a file. If the file already exists on the service, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the file. @@ -1880,7 +1881,7 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count) } /// - /// Returns a task that performs an asynchronous operation to upload the contents of a byte array to a file. + /// Returns a task that performs an asynchronous operation to upload the contents of a byte array to a file. If the file already exists on the service, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the file. @@ -1894,7 +1895,7 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, Cancel } /// - /// Returns a task that performs an asynchronous operation to upload the contents of a byte array to a file. + /// Returns a task that performs an asynchronous operation to upload the contents of a byte array to a file. If the file already exists on the service, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the file. @@ -1910,7 +1911,7 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, Access } /// - /// Returns a task that performs an asynchronous operation to upload the contents of a byte array to a file. + /// Returns a task that performs an asynchronous operation to upload the contents of a byte array to a file. If the file already exists on the service, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the file. @@ -1929,7 +1930,7 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, Access #if SYNC /// - /// Uploads a string of text to a file. + /// Uploads a string of text to a file. If the file already exists on the service, it will be overwritten. /// /// The text to upload. /// An object that indicates the text encoding to use. If null, UTF-8 will be used. @@ -1947,7 +1948,7 @@ public void UploadText(string content, Encoding encoding = null, AccessCondition #endif /// - /// Begins an asynchronous operation to upload a string of text to a file. + /// Begins an asynchronous operation to upload a string of text to a file. If the file already exists on the service, it will be overwritten. /// /// The text to upload. /// The callback delegate that will receive notification when the asynchronous operation completes. @@ -1960,7 +1961,7 @@ public ICancellableAsyncResult BeginUploadText(string content, AsyncCallback cal } /// - /// Begins an asynchronous operation to upload a string of text to a file. + /// Begins an asynchronous operation to upload a string of text to a file. If the file already exists on the service, it will be overwritten. /// /// The text to upload. /// An object that indicates the text encoding to use. If null, UTF-8 will be used. @@ -1990,7 +1991,7 @@ public void EndUploadText(IAsyncResult asyncResult) #if TASK /// - /// Returns a task that performs an asynchronous operation to upload a string of text to a file. + /// Returns a task that performs an asynchronous operation to upload a string of text to a file. If the file already exists on the service, it will be overwritten. /// /// The text to upload. /// A object that represents the current operation. @@ -2001,7 +2002,7 @@ public Task UploadTextAsync(string content) } /// - /// Returns a task that performs an asynchronous operation to upload a string of text to a file. + /// Returns a task that performs an asynchronous operation to upload a string of text to a file. If the file already exists on the service, it will be overwritten. /// /// The text to upload. /// A to observe while waiting for a task to complete. @@ -2013,7 +2014,7 @@ public Task UploadTextAsync(string content, CancellationToken cancellationToken) } /// - /// Returns a task that performs an asynchronous operation to upload a string of text to a file. + /// Returns a task that performs an asynchronous operation to upload a string of text to a file. If the file already exists on the service, it will be overwritten. /// /// The text to upload. /// An object that indicates the text encoding to use. If null, UTF-8 will be used. @@ -2028,7 +2029,7 @@ public Task UploadTextAsync(string content, Encoding encoding, AccessCondition a } /// - /// Returns a task that performs an asynchronous operation to upload a string of text to a file. + /// Returns a task that performs an asynchronous operation to upload a string of text to a file. If the file already exists on the service, it will be overwritten. /// /// The text to upload. /// An object that indicates the text encoding to use. If null, UTF-8 will be used. @@ -2046,7 +2047,7 @@ public Task UploadTextAsync(string content, Encoding encoding, AccessCondition a #if SYNC /// - /// Creates a file. + /// Creates a file. If the file already exists, it will be overwritten.3584 /// /// The maximum size of the file, in bytes. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -2064,7 +2065,7 @@ public void Create(long size, AccessCondition accessCondition = null, FileReques #endif /// - /// Begins an asynchronous operation to create a file. + /// Begins an asynchronous operation to create a file. If the file already exists, it will be overwritten. /// /// The maximum size of the file, in bytes. /// The callback delegate that will receive notification when the asynchronous operation completes. @@ -2077,7 +2078,7 @@ public ICancellableAsyncResult BeginCreate(long size, AsyncCallback callback, ob } /// - /// Begins an asynchronous operation to create a file. + /// Begins an asynchronous operation to create a file. If the file already exists, it will be overwritten. /// /// The maximum size of the file, in bytes. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -2109,7 +2110,7 @@ public void EndCreate(IAsyncResult asyncResult) #if TASK /// - /// Returns a task that performs an asynchronous operation to create a file. + /// Returns a task that performs an asynchronous operation to create a file. If the file already exists, it will be overwritten. /// /// The maximum size of the file, in bytes. /// A object that represents the current operation. @@ -2120,7 +2121,7 @@ public Task CreateAsync(long size) } /// - /// Returns a task that performs an asynchronous operation to create a file. + /// Returns a task that performs an asynchronous operation to create a file. If the file already exists, it will be overwritten. /// /// The maximum size of the file, in bytes. /// A to observe while waiting for a task to complete. @@ -2132,7 +2133,7 @@ public Task CreateAsync(long size, CancellationToken cancellationToken) } /// - /// Returns a task that performs an asynchronous operation to create a file. + /// Returns a task that performs an asynchronous operation to create a file. If the file already exists, it will be overwritten. /// /// The maximum size of the file, in bytes. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -2146,7 +2147,7 @@ public Task CreateAsync(long size, AccessCondition accessCondition, FileRequestO } /// - /// Returns a task that performs an asynchronous operation to create a file. + /// Returns a task that performs an asynchronous operation to create a file. If the file already exists, it will be overwritten. /// /// The maximum size of the file, in bytes. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -3195,36 +3196,49 @@ public void WriteRange(Stream rangeData, long startOffset, string contentMD5 = n operationContext = operationContext ?? new OperationContext(); Stream seekableStream = rangeData; - if (!rangeData.CanSeek || requiresContentMD5) - { - ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions); + bool seekableStreamCreated = false; - Stream writeToStream; - if (rangeData.CanSeek) - { - writeToStream = Stream.Null; - } - else + try + { + if (!rangeData.CanSeek || requiresContentMD5) { - seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); - writeToStream = seekableStream; - } + ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions); - long startPosition = seekableStream.Position; - StreamDescriptor streamCopyState = new StreamDescriptor(); - rangeData.WriteToSync(writeToStream, null /* copyLength */, Constants.MaxBlockSize, requiresContentMD5, true, tempExecutionState, streamCopyState); - seekableStream.Position = startPosition; + Stream writeToStream; + if (rangeData.CanSeek) + { + writeToStream = Stream.Null; + } + else + { + seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); + seekableStreamCreated = true; + writeToStream = seekableStream; + } + + long startPosition = seekableStream.Position; + StreamDescriptor streamCopyState = new StreamDescriptor(); + rangeData.WriteToSync(writeToStream, null /* copyLength */, Constants.MaxBlockSize, requiresContentMD5, true, tempExecutionState, streamCopyState); + seekableStream.Position = startPosition; + + if (requiresContentMD5) + { + contentMD5 = streamCopyState.Md5; + } + } - if (requiresContentMD5) + Executor.ExecuteSync( + this.PutRangeImpl(seekableStream, startOffset, contentMD5, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext); + } + finally + { + if (seekableStreamCreated) { - contentMD5 = streamCopyState.Md5; + seekableStream.Dispose(); } } - - Executor.ExecuteSync( - this.PutRangeImpl(seekableStream, startOffset, contentMD5, accessCondition, modifiedOptions), - modifiedOptions.RetryPolicy, - operationContext); } #endif @@ -3286,6 +3300,7 @@ public ICancellableAsyncResult BeginWriteRange(Stream rangeData, long startOffse else { seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); + storageAsyncResult.OperationState = seekableStream; writeToStream = seekableStream; } @@ -3361,7 +3376,19 @@ private void WriteRangeHandler(Stream rangeData, long startOffset, string conten public void EndWriteRange(IAsyncResult asyncResult) { StorageAsyncResult storageAsyncResult = (StorageAsyncResult)asyncResult; - storageAsyncResult.End(); + + try + { + storageAsyncResult.End(); + } + finally + { + if (storageAsyncResult.OperationState != null) + { + MultiBufferMemoryStream stream = (MultiBufferMemoryStream)storageAsyncResult.OperationState; + stream.Dispose(); + } + } } #if TASK @@ -3554,6 +3581,464 @@ public Task ClearRangeAsync(long startOffset, long length, AccessCondition acces } #endif +#if SYNC + /// + /// Begins an operation to start copying another Azure file or blob's contents, properties, and metadata to this Azure file. + /// + /// The of the source blob or file. + /// An object that represents the access conditions for the source object. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the file's ETag, last-modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] + public string StartCopy(Uri source, AccessCondition sourceAccessCondition = null, AccessCondition destAccessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) + { + CommonUtility.AssertNotNull("source", source); + + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Executor.ExecuteSync( + this.StartCopyImpl(source, sourceAccessCondition, destAccessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext); + } + + /// + /// Begins an operation to start copying another file's contents, properties, and metadata to this file. + /// + /// A object. + /// An object that represents the access conditions for the source file. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the file's ETag, last-modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] + public string StartCopy(CloudFile source, AccessCondition sourceAccessCondition = null, AccessCondition destAccessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) + { + return this.StartCopy(CloudFile.SourceFileToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext); + } + + /// + /// Begins an operation to start copying a blob's contents, properties, and metadata to this Azure file. + /// + /// The of the source blob. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the file's ETag, last-modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] + public string StartCopy(CloudBlob source, AccessCondition sourceAccessCondition = null, AccessCondition destAccessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) + { + return this.StartCopy(CloudBlob.SourceBlobToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext); + } + +#endif + /// + /// Begins an asynchronous operation to start copying another Azure file or blob's contents, properties, and metadata to this Azure file. + /// + /// The of the source blob or file. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginStartCopy(Uri source, AsyncCallback callback, object state) + { + return this.BeginStartCopy(source, null /* sourceAccessCondition */, null /* destAccessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to start copying another file's contents, properties, and metadata to this file. + /// + /// A object. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginStartCopy(CloudFile source, AsyncCallback callback, object state) + { + return this.BeginStartCopy(CloudFile.SourceFileToUri(source), callback, state); + } + + /// + /// Begins an asynchronous operation to start copying a blob's contents, properties, and metadata to this Azure file. + /// + /// The that is the source blob. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginStartCopy(CloudBlob source, AsyncCallback callback, object state) + { + return this.BeginStartCopy(CloudBlob.SourceBlobToUri(source), callback, state); + } + + /// + /// Begins an asynchronous operation to start copying another Azure file or blob's contents, properties, and metadata to this Azure file. + /// + /// The of the source blob or file. + /// An object that represents the access conditions for the source object. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginStartCopy(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + CommonUtility.AssertNotNull("source", source); + + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Executor.BeginExecuteAsync( + this.StartCopyImpl(source, sourceAccessCondition, destAccessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + callback, + state); + } + + /// + /// Begins an asynchronous operation to start copying another file's contents, properties, and metadata to this file. + /// + /// A object. + /// An object that represents the access conditions for the source file. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginStartCopy(CloudFile source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginStartCopy(CloudFile.SourceFileToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext, callback, state); + } + + /// + /// Begins an asynchronous operation to start copying a blob's contents, properties, and metadata to this Azure file. + /// + /// The that is the source blob. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginStartCopy(CloudBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + return this.BeginStartCopy(CloudBlob.SourceBlobToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext, callback, state); + } + + /// + /// Ends an asynchronous operation to start copying another Azure file or blob's contents, properties, and metadata to this Azure file. + /// + /// An that references the pending asynchronous operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the file's ETag, last-modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + public string EndStartCopy(IAsyncResult asyncResult) + { + return Executor.EndExecuteAsync(asyncResult); + } + +#if TASK + /// + /// Initiates an asynchronous operation to start copying another Azure file or blob's contents, properties, and metadata to this Azure file. + /// + /// The of the source blob or file. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(Uri source) + { + return this.StartCopyAsync(source, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to start copying another Azure file or blob's contents, properties, and metadata to this Azure file. + /// + /// The of the source blob or file. + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(Uri source, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to start copying another file's contents, properties, and metadata to this file. + /// + /// A object. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudFile source) + { + return this.StartCopyAsync(source, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to start copying a blob's contents, properties, and metadata to this Azure file. + /// + /// The that is the source blob. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudBlob source) + { + return this.StartCopyAsync(source, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to start copying another file's contents, properties, and metadata to this file. + /// + /// A object. + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudFile source, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to start copying a blob's contents, properties, and metadata to this Azure file. + /// + /// The that is the source blob. + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudBlob source, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to start copying another Azure file or blob's contents, properties, and metadata to this Azure file. + /// + /// The of the source blob or file. + /// An object that represents the access conditions for the source object. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext) + { + return this.StartCopyAsync(source, sourceAccessCondition, destAccessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to start copying another Azure file or blob's contents, properties, and metadata to this Azure file. + /// + /// The of the source blob or file. + /// An object that represents the access conditions for the source object. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to start copying another file's contents, properties, and metadata to this file. + /// + /// A object. + /// An object that represents the access conditions for the source file. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudFile source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext) + { + return this.StartCopyAsync(source, sourceAccessCondition, destAccessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to start copying a blob's contents, properties, and metadata to this Azure file. + /// + /// The that is the source blob. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext) + { + return this.StartCopyAsync(source, sourceAccessCondition, destAccessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to start copying another file's contents, properties, and metadata to this file. + /// + /// A object. + /// An object that represents the access conditions for the source file. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudFile source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to start copying a blob's contents, properties, and metadata to this Azure file. + /// + /// The that is the source blob. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object of type string that represents the asynchronous operation. + [DoesServiceRequest] + public Task StartCopyAsync(CloudBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginStartCopy, this.EndStartCopy, source, sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); + } +#endif + +#if SYNC + /// + /// Aborts an ongoing copy operation. + /// + /// A string identifying the copy operation. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] + public void AbortCopy(string copyId, AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + Executor.ExecuteSync( + this.AbortCopyImpl(copyId, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext); + } +#endif + + /// + /// Begins an asynchronous operation to abort an ongoing copy operation. + /// + /// A string identifying the copy operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginAbortCopy(string copyId, AsyncCallback callback, object state) + { + return this.BeginAbortCopy(copyId, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to abort an ongoing copy operation. + /// + /// A string identifying the copy operation. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginAbortCopy(string copyId, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Executor.BeginExecuteAsync( + this.AbortCopyImpl(copyId, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + callback, + state); + } + + /// + /// Ends an asynchronous operation to abort an ongoing copy operation. + /// + /// An that references the pending asynchronous operation. + public void EndAbortCopy(IAsyncResult asyncResult) + { + Executor.EndExecuteAsync(asyncResult); + } + +#if TASK + /// + /// Initiates an asynchronous operation to abort an ongoing copy operation. + /// + /// A string identifying the copy operation. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task AbortCopyAsync(string copyId) + { + return this.AbortCopyAsync(copyId, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to abort an ongoing copy operation. + /// + /// A string identifying the copy operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task AbortCopyAsync(string copyId, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginAbortCopy, this.EndAbortCopy, copyId, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to abort an ongoing copy operation. + /// + /// A string identifying the copy operation. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task AbortCopyAsync(string copyId, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + return this.AbortCopyAsync(copyId, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to abort an ongoing copy operation. + /// + /// A string identifying the copy operation. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task AbortCopyAsync(string copyId, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginAbortCopy, this.EndAbortCopy, copyId, accessCondition, options, operationContext, cancellationToken); + } +#endif + /// /// Implements getting the stream without specifying a range. /// @@ -3954,6 +4439,66 @@ private RESTCommand ClearRangeImpl(long startOffset, long length, Acce return putCmd; } + /// + /// Implementation of the StartCopy method. Result is a CloudFileAttributes object derived from the response headers. + /// + /// The URI of the source object. + /// An object that represents the access conditions for the source object. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// + /// A that starts to copy the file. + /// + /// sourceAccessCondition + private RESTCommand StartCopyImpl(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options) + { + if (sourceAccessCondition != null && !string.IsNullOrEmpty(sourceAccessCondition.LeaseId)) + { + throw new ArgumentException(SR.LeaseConditionOnSource, "sourceAccessCondition"); + } + + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => FileHttpWebRequestFactory.CopyFrom(uri, serverTimeout, source, sourceAccessCondition, destAccessCondition, useVersionHeader, ctx); + putCmd.SetHeaders = (r, ctx) => FileHttpWebRequestFactory.AddMetadata(r, this.attributes.Metadata); + putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Accepted, resp, null /* retVal */, cmd, ex); + CopyState state = FileHttpResponseParsers.GetCopyAttributes(resp); + this.attributes.Properties = FileHttpResponseParsers.GetProperties(resp); + this.attributes.Metadata = FileHttpResponseParsers.GetMetadata(resp); + this.attributes.CopyState = state; + return state.CopyId; + }; + + return putCmd; + } + + /// + /// Implementation of the AbortCopy method. No result is produced. + /// + /// The copy ID of the copy operation to abort. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// + /// A that aborts the copy. + /// + private RESTCommand AbortCopyImpl(string copyId, AccessCondition accessCondition, FileRequestOptions options) + { + CommonUtility.AssertNotNull("copyId", copyId); + + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => FileHttpWebRequestFactory.AbortCopy(uri, serverTimeout, copyId, accessCondition, useVersionHeader, ctx); + putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.NoContent, resp, NullType.Value, cmd, ex); + + return putCmd; + } + /// /// Called when the asynchronous operation to commit the file started by UploadFromStream finishes. /// @@ -3976,6 +4521,17 @@ private static void FileStreamCommitCallback(IAsyncResult result) } } + /// + /// Converts the source blob of a copy operation to an appropriate access URI, taking Shared Access Signature credentials into account. + /// + /// The source blob. + /// A URI addressing the source blob, using SAS if appropriate. + internal static Uri SourceFileToUri(CloudFile source) + { + CommonUtility.AssertNotNull("source", source); + return source.ServiceClient.Credentials.TransformUri(source.Uri); + } + /// /// Updates this file with the given attributes a the end of a fetch attributes operation. /// @@ -3984,7 +4540,8 @@ private static void FileStreamCommitCallback(IAsyncResult result) private void UpdateAfterFetchAttributes(HttpWebResponse response, bool ignoreMD5) { FileProperties properties = FileHttpResponseParsers.GetProperties(response); - + CopyState state = FileHttpResponseParsers.GetCopyAttributes(response); + if (ignoreMD5) { properties.ContentMD5 = this.attributes.Properties.ContentMD5; @@ -3992,6 +4549,7 @@ private void UpdateAfterFetchAttributes(HttpWebResponse response, bool ignoreMD5 this.attributes.Properties = properties; this.attributes.Metadata = FileHttpResponseParsers.GetMetadata(response); + this.attributes.CopyState = state; } /// diff --git a/Lib/ClassLibraryCommon/File/CloudFileClient.cs b/Lib/ClassLibraryCommon/File/CloudFileClient.cs index e1a7689ad..9539708ac 100644 --- a/Lib/ClassLibraryCommon/File/CloudFileClient.cs +++ b/Lib/ClassLibraryCommon/File/CloudFileClient.cs @@ -18,12 +18,14 @@ namespace Microsoft.WindowsAzure.Storage.File { using Microsoft.WindowsAzure.Storage.Auth.Protocol; + using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Executor; using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.File.Protocol; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Collections.Generic; + using System.IO; using System.Linq; using System.Net; using System.Threading; @@ -324,6 +326,231 @@ public Task ListSharesSegmentedAsync(string prefix, ShareLis } #endif + /// + /// Begins an asynchronous operation to get service properties for the File service. + /// + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object to be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginGetServiceProperties(AsyncCallback callback, object state) + { + return this.BeginGetServiceProperties(null /* requestOptions */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to get service properties for the File service. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object to be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginGetServiceProperties(FileRequestOptions requestOptions, OperationContext operationContext, AsyncCallback callback, object state) + { + requestOptions = FileRequestOptions.ApplyDefaults(requestOptions, this); + operationContext = operationContext ?? new OperationContext(); + return Executor.BeginExecuteAsync( + this.GetServicePropertiesImpl(requestOptions), + requestOptions.RetryPolicy, + operationContext, + callback, + state); + } + + /// + /// Ends an asynchronous operation to get service properties for the File service. + /// + /// An that references the pending asynchronous operation. + /// A object. + public FileServiceProperties EndGetServiceProperties(IAsyncResult asyncResult) + { + return Executor.EndExecuteAsync(asyncResult); + } + +#if TASK + /// + /// Initiates an asynchronous operation to get service properties for the File service. + /// + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task GetServicePropertiesAsync() + { + return this.GetServicePropertiesAsync(CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to get service properties for the File service. + /// + /// A to observe while waiting for a task to complete. + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task GetServicePropertiesAsync(CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginGetServiceProperties, this.EndGetServiceProperties, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to get service properties for the File service. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task GetServicePropertiesAsync(FileRequestOptions requestOptions, OperationContext operationContext) + { + return this.GetServicePropertiesAsync(requestOptions, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to get service properties for the File service. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task GetServicePropertiesAsync(FileRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginGetServiceProperties, this.EndGetServiceProperties, requestOptions, operationContext, cancellationToken); + } +#endif + +#if SYNC + /// + /// Gets service properties for the File service. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object. + [DoesServiceRequest] + public FileServiceProperties GetServiceProperties(FileRequestOptions requestOptions = null, OperationContext operationContext = null) + { + requestOptions = FileRequestOptions.ApplyDefaults(requestOptions, this); + operationContext = operationContext ?? new OperationContext(); + return Executor.ExecuteSync( + this.GetServicePropertiesImpl(requestOptions), + requestOptions.RetryPolicy, + operationContext); + } +#endif + + /// + /// Begins an asynchronous operation to set service properties for the File service. + /// + /// A object. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object to be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginSetServiceProperties(FileServiceProperties properties, AsyncCallback callback, object state) + { + return this.BeginSetServiceProperties(properties, null /* requestOptions */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to set service properties for the File service. + /// + /// A object. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object to be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginSetServiceProperties(FileServiceProperties properties, FileRequestOptions requestOptions, OperationContext operationContext, AsyncCallback callback, object state) + { + requestOptions = FileRequestOptions.ApplyDefaults(requestOptions, this); + operationContext = operationContext ?? new OperationContext(); + return Executor.BeginExecuteAsync( + this.SetServicePropertiesImpl(properties, requestOptions), + requestOptions.RetryPolicy, + operationContext, + callback, + state); + } + + /// + /// Ends an asynchronous operation to set service properties for the Blob service. + /// + /// An that references the pending asynchronous operation. + public void EndSetServiceProperties(IAsyncResult asyncResult) + { + Executor.EndExecuteAsync(asyncResult); + } + +#if TASK + /// + /// Initiates an asynchronous operation that sets service properties for the Blob service. + /// + /// A object. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task SetServicePropertiesAsync(FileServiceProperties properties) + { + return this.SetServicePropertiesAsync(properties, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation that sets service properties for the Blob service. + /// + /// A object. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task SetServicePropertiesAsync(FileServiceProperties properties, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginSetServiceProperties, this.EndSetServiceProperties, properties, cancellationToken); + } + + /// + /// Initiates an asynchronous operation that sets service properties for the File service. + /// + /// A object. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task SetServicePropertiesAsync(FileServiceProperties properties, FileRequestOptions requestOptions, OperationContext operationContext) + { + return this.SetServicePropertiesAsync(properties, requestOptions, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation that sets service properties for the File service. + /// + /// A object. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task SetServicePropertiesAsync(FileServiceProperties properties, FileRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginSetServiceProperties, this.EndSetServiceProperties, properties, requestOptions, operationContext, cancellationToken); + } +#endif + +#if SYNC + /// + /// Sets service properties for the File service. + /// + /// A object. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] + public void SetServiceProperties(FileServiceProperties properties, FileRequestOptions requestOptions = null, OperationContext operationContext = null) + { + requestOptions = FileRequestOptions.ApplyDefaults(requestOptions, this); + operationContext = operationContext ?? new OperationContext(); + Executor.ExecuteSync( + this.SetServicePropertiesImpl(properties, requestOptions), + requestOptions.RetryPolicy, + operationContext); + } +#endif + /// /// Core implementation for the ListShares method. /// @@ -372,5 +599,49 @@ private RESTCommand> ListSharesImpl(string prefix, return getCmd; } + + private RESTCommand GetServicePropertiesImpl(FileRequestOptions requestOptions) + { + RESTCommand retCmd = new RESTCommand(this.Credentials, this.StorageUri); + retCmd.CommandLocationMode = CommandLocationMode.PrimaryOrSecondary; + retCmd.BuildRequestDelegate = FileHttpWebRequestFactory.GetServiceProperties; + retCmd.SignRequest = this.AuthenticationHandler.SignRequest; + retCmd.RetrieveResponseStream = true; + retCmd.PreProcessResponse = + (cmd, resp, ex, ctx) => + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, null /* retVal */, cmd, ex); + retCmd.PostProcessResponse = + (cmd, resp, ctx) => FileHttpResponseParsers.ReadServiceProperties(cmd.ResponseStream); + requestOptions.ApplyToStorageCommand(retCmd); + return retCmd; + } + + private RESTCommand SetServicePropertiesImpl(FileServiceProperties properties, FileRequestOptions requestOptions) + { + MultiBufferMemoryStream str = new MultiBufferMemoryStream(null /* bufferManager */, (int)(1 * Constants.KB)); + try + { + properties.WriteServiceProperties(str); + } + catch (InvalidOperationException invalidOpException) + { + str.Dispose(); + throw new ArgumentException(invalidOpException.Message, "properties"); + } + + str.Seek(0, SeekOrigin.Begin); + + RESTCommand retCmd = new RESTCommand(this.Credentials, this.StorageUri); + retCmd.SendStream = str; + retCmd.StreamToDispose = str; + retCmd.BuildRequestDelegate = FileHttpWebRequestFactory.SetServiceProperties; + retCmd.RecoveryAction = RecoveryActions.RewindStream; + retCmd.SignRequest = this.AuthenticationHandler.SignRequest; + retCmd.PreProcessResponse = + (cmd, resp, ex, ctx) => + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Accepted, resp, NullType.Value, cmd, ex); + requestOptions.ApplyToStorageCommand(retCmd); + return retCmd; + } } } diff --git a/Lib/ClassLibraryCommon/File/CloudFileDirectory.cs b/Lib/ClassLibraryCommon/File/CloudFileDirectory.cs index c805e7ba0..972bd3517 100644 --- a/Lib/ClassLibraryCommon/File/CloudFileDirectory.cs +++ b/Lib/ClassLibraryCommon/File/CloudFileDirectory.cs @@ -1056,6 +1056,116 @@ public Task ListFilesAndDirectoriesSegmentedAsync(int? maxRes } #endif +#if SYNC + /// + /// Updates the directory's metadata. + /// + /// An object that represents the access conditions for the directory. If null, no condition is used. + /// An object that specifies additional options for the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] + public void SetMetadata(AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + Executor.ExecuteSync( + this.SetMetadataImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext); + } +#endif + + /// + /// Begins an asynchronous operation to update the directory's metadata. + /// + /// The callback delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginSetMetadata(AsyncCallback callback, object state) + { + return this.BeginSetMetadata(null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to update the directory's metadata. + /// + /// An object that represents the access conditions for the directory. If null, no condition is used. + /// An object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// The callback delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginSetMetadata(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Executor.BeginExecuteAsync( + this.SetMetadataImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + callback, + state); + } + + /// + /// Ends an asynchronous operation to update the directory's metadata. + /// + /// An that references the pending asynchronous operation. + public void EndSetMetadata(IAsyncResult asyncResult) + { + Executor.EndExecuteAsync(asyncResult); + } + +#if TASK + /// + /// Returns a task that performs an asynchronous operation to update the directory's metadata. + /// + /// A object that represents the current operation. + [DoesServiceRequest] + public Task SetMetadataAsync() + { + return this.SetMetadataAsync(CancellationToken.None); + } + + /// + /// Returns a task that performs an asynchronous operation to update the directory's metadata. + /// + /// A to observe while waiting for a task to complete. + /// A object that represents the current operation. + [DoesServiceRequest] + public Task SetMetadataAsync(CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginSetMetadata, this.EndSetMetadata, cancellationToken); + } + + /// + /// Returns a task that performs an asynchronous operation to update the directory's metadata. + /// + /// An object that represents the access conditions for the directory. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the current operation. + [DoesServiceRequest] + public Task SetMetadataAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + return this.SetMetadataAsync(accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Returns a task that performs an asynchronous operation to update the directory's metadata. + /// + /// An object that represents the access conditions for the directory. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the current operation. + [DoesServiceRequest] + public Task SetMetadataAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginSetMetadata, this.EndSetMetadata, accessCondition, options, operationContext, cancellationToken); + } +#endif + /// /// Implementation for the Create method. /// @@ -1067,6 +1177,7 @@ private RESTCommand CreateDirectoryImpl(FileRequestOptions options) options.ApplyToStorageCommand(putCmd); putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => DirectoryHttpWebRequestFactory.Create(uri, serverTimeout, useVersionHeader, ctx); + putCmd.SetHeaders = (r, ctx) => DirectoryHttpWebRequestFactory.AddMetadata(r, this.Metadata); putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => { @@ -1145,6 +1256,7 @@ private RESTCommand FetchAttributesImpl(AccessCondition accessConditio { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); this.Properties = DirectoryHttpResponseParsers.GetProperties(resp); + this.Metadata = DirectoryHttpResponseParsers.GetMetadata(resp); return NullType.Value; }; @@ -1197,6 +1309,30 @@ private RESTCommand> ListFilesAndDirectoriesImpl(in return getCmd; } + /// + /// Implementation for the SetMetadata method. + /// + /// An object that represents the access conditions for the directory. If null, no condition is used. + /// A object that specifies additional options for the request. + /// A that sets the metadata. + private RESTCommand SetMetadataImpl(AccessCondition accessCondition, FileRequestOptions options) + { + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => DirectoryHttpWebRequestFactory.SetMetadata(uri, serverTimeout, accessCondition, useVersionHeader, ctx); + putCmd.SetHeaders = (r, ctx) => DirectoryHttpWebRequestFactory.AddMetadata(r, this.Metadata); + putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); + this.UpdateETagAndLastModified(resp); + return NullType.Value; + }; + + return putCmd; + } + /// /// Retrieve ETag and LastModified date time from response. /// diff --git a/Lib/ClassLibraryCommon/File/CloudFileShare.cs b/Lib/ClassLibraryCommon/File/CloudFileShare.cs index d069dc57f..903c494fa 100644 --- a/Lib/ClassLibraryCommon/File/CloudFileShare.cs +++ b/Lib/ClassLibraryCommon/File/CloudFileShare.cs @@ -23,6 +23,8 @@ namespace Microsoft.WindowsAzure.Storage.File using Microsoft.WindowsAzure.Storage.File.Protocol; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; + using System.Collections.Generic; + using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; @@ -920,6 +922,228 @@ public Task FetchAttributesAsync(AccessCondition accessCondition, FileRequestOpt } #endif +#if SYNC + /// + /// Gets the permissions settings for the share. + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + /// A object. + [DoesServiceRequest] + public FileSharePermissions GetPermissions(AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Executor.ExecuteSync( + this.GetPermissionsImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext); + } +#endif + + /// + /// Begins an asynchronous request to get the permissions settings for the share. + /// + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginGetPermissions(AsyncCallback callback, object state) + { + return this.BeginGetPermissions(null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous request to get the permissions settings for the share. + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginGetPermissions(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Executor.BeginExecuteAsync( + this.GetPermissionsImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + callback, + state); + } + + /// + /// Returns the asynchronous result of the request to get the permissions settings for the share. + /// + /// An that references the pending asynchronous operation. + /// A object. + public FileSharePermissions EndGetPermissions(IAsyncResult asyncResult) + { + return Executor.EndExecuteAsync(asyncResult); + } + +#if TASK + /// + /// Initiates an asynchronous operation that gets the permissions settings for the share. + /// + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task GetPermissionsAsync() + { + return this.GetPermissionsAsync(CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation that gets the permissions settings for the share. + /// + /// A to observe while waiting for a task to complete. + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task GetPermissionsAsync(CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginGetPermissions, this.EndGetPermissions, cancellationToken); + } + + /// + /// Initiates an asynchronous operation that gets the permissions settings for the share. + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task GetPermissionsAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + return this.GetPermissionsAsync(accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation that gets the permissions settings for the share. + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task GetPermissionsAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginGetPermissions, this.EndGetPermissions, accessCondition, options, operationContext, cancellationToken); + } +#endif + +#if SYNC + /// + /// Gets stats for the share. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object. + [DoesServiceRequest] + public ShareStats GetStats(FileRequestOptions options = null, OperationContext operationContext = null) + { + options = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + operationContext = operationContext ?? new OperationContext(); + return Executor.ExecuteSync( + this.GetStatsImpl(options), + options.RetryPolicy, + operationContext); + } +#endif + + /// + /// Begins an asynchronous operation to get stats for the share. + /// + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object to be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginGetStats(AsyncCallback callback, object state) + { + return this.BeginGetStats(null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to get stats for the share. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object to be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginGetStats(FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + options = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + operationContext = operationContext ?? new OperationContext(); + return Executor.BeginExecuteAsync( + this.GetStatsImpl(options), + options.RetryPolicy, + operationContext, + callback, + state); + } + + /// + /// Ends an asynchronous operation to get stats for the share. + /// + /// An that references the pending asynchronous operation. + /// A object. + public ShareStats EndGetStats(IAsyncResult asyncResult) + { + return Executor.EndExecuteAsync(asyncResult); + } + +#if TASK + /// + /// Initiates an asynchronous operation to get stats for the share. + /// + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task GetStatsAsync() + { + return this.GetStatsAsync(CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to get stats for the share. + /// + /// A to observe while waiting for a task to complete. + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task GetStatsAsync(CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginGetStats, this.EndGetStats, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to get stats for the share. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task GetStatsAsync(FileRequestOptions options, OperationContext operationContext) + { + return this.GetStatsAsync(options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to get stats for the share. + /// + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object of type that represents the asynchronous operation. + [DoesServiceRequest] + public Task GetStatsAsync(FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromApm(this.BeginGetStats, this.EndGetStats, options, operationContext, cancellationToken); + } +#endif + #if SYNC /// /// Sets the share's user-defined metadata. @@ -1030,6 +1254,233 @@ public Task SetMetadataAsync(AccessCondition accessCondition, FileRequestOptions } #endif +#if SYNC + /// + /// Sets permissions for the share. + /// + /// A object. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] + public void SetPermissions(FileSharePermissions permissions, AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + Executor.ExecuteSync( + this.SetPermissionsImpl(permissions, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext); + } +#endif + + /// + /// Begins an asynchronous request to set permissions for the share. + /// + /// The permissions to apply to the share. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginSetPermissions(FileSharePermissions permissions, AsyncCallback callback, object state) + { + return this.BeginSetPermissions(permissions, null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous request to set permissions for the share. + /// + /// The permissions to apply to the share. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginSetPermissions(FileSharePermissions permissions, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Executor.BeginExecuteAsync( + this.SetPermissionsImpl(permissions, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + callback, + state); + } + + /// + /// Returns the result of an asynchronous request to set permissions for the share. + /// + /// An that references the pending asynchronous operation. + public void EndSetPermissions(IAsyncResult asyncResult) + { + Executor.EndExecuteAsync(asyncResult); + } + +#if TASK + /// + /// Initiates an asynchronous operation that sets permissions for the share. + /// + /// The permissions to apply to the share. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task SetPermissionsAsync(FileSharePermissions permissions) + { + return this.SetPermissionsAsync(permissions, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation that sets permissions for the share. + /// + /// The permissions to apply to the share. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task SetPermissionsAsync(FileSharePermissions permissions, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginSetPermissions, this.EndSetPermissions, permissions, cancellationToken); + } + + /// + /// Initiates an asynchronous operation that sets permissions for the share. + /// + /// The permissions to apply to the share. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task SetPermissionsAsync(FileSharePermissions permissions, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + return this.SetPermissionsAsync(permissions, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation that sets permissions for the share. + /// + /// The permissions to apply to the share. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task SetPermissionsAsync(FileSharePermissions permissions, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginSetPermissions, this.EndSetPermissions, permissions, accessCondition, options, operationContext, cancellationToken); + } +#endif + +#if SYNC + /// + /// Updates the share's properties. + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. If null, default options are applied to the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] + public void SetProperties(AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + Executor.ExecuteSync( + this.SetPropertiesImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext); + } +#endif + + /// + /// Begins an asynchronous operation to update the share's properties. + /// + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginSetProperties(AsyncCallback callback, object state) + { + return this.BeginSetProperties(null /* accessCondition */, null /* options */, null /* operationContext */, callback, state); + } + + /// + /// Begins an asynchronous operation to update the share's properties. + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An delegate that will receive notification when the asynchronous operation completes. + /// A user-defined object that will be passed to the callback delegate. + /// An that references the asynchronous operation. + [DoesServiceRequest] + public ICancellableAsyncResult BeginSetProperties(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, AsyncCallback callback, object state) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Executor.BeginExecuteAsync( + this.SetPropertiesImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + callback, + state); + } + + /// + /// Ends an asynchronous operation to update the share's properties. + /// + /// An that references the pending asynchronous operation. + public void EndSetProperties(IAsyncResult asyncResult) + { + Executor.EndExecuteAsync(asyncResult); + } + +#if TASK + /// + /// Initiates an asynchronous operation to update the share's properties. + /// + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task SetPropertiesAsync() + { + return this.SetPropertiesAsync(CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to update the share's properties. + /// + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task SetPropertiesAsync(CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginSetProperties, this.EndSetProperties, cancellationToken); + } + + /// + /// Initiates an asynchronous operation to update the share's properties. + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task SetPropertiesAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + return this.SetPropertiesAsync(accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Initiates an asynchronous operation to update the share's properties. + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A object that represents the asynchronous operation. + [DoesServiceRequest] + public Task SetPropertiesAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return AsyncExtensions.TaskFromVoidApm(this.BeginSetProperties, this.EndSetProperties, accessCondition, options, operationContext, cancellationToken); + } +#endif + /// /// Implementation for the Create method. /// @@ -1040,20 +1491,19 @@ private RESTCommand CreateShareImpl(FileRequestOptions options) RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); options.ApplyToStorageCommand(putCmd); - putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => ShareHttpWebRequestFactory.Create(uri, serverTimeout, useVersionHeader, ctx); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => ShareHttpWebRequestFactory.Create(uri, this.Properties, serverTimeout, useVersionHeader, ctx); putCmd.SetHeaders = (r, ctx) => ShareHttpWebRequestFactory.AddMetadata(r, this.Metadata); putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); - this.Properties = ShareHttpResponseParsers.GetProperties(resp); - this.Metadata = ShareHttpResponseParsers.GetMetadata(resp); + this.UpdateETagAndLastModified(resp); return NullType.Value; }; return putCmd; } - + /// /// Implementation for the Delete method. /// @@ -1126,6 +1576,57 @@ private RESTCommand ExistsImpl(FileRequestOptions options) return getCmd; } + /// + /// Implementation for the GetPermissions method. + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// A that gets the permissions. + private RESTCommand GetPermissionsImpl(AccessCondition accessCondition, FileRequestOptions options) + { + FileSharePermissions shareAcl = null; + + RESTCommand getCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); + + options.ApplyToStorageCommand(getCmd); + getCmd.CommandLocationMode = CommandLocationMode.PrimaryOrSecondary; + getCmd.RetrieveResponseStream = true; + getCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => ShareHttpWebRequestFactory.GetAcl(uri, serverTimeout, accessCondition, useVersionHeader, ctx); + getCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; + getCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, null /* retVal */, cmd, ex); + shareAcl = new FileSharePermissions(); + return shareAcl; + }; + getCmd.PostProcessResponse = (cmd, resp, ctx) => + { + ShareHttpResponseParsers.ReadSharedAccessIdentifiers(cmd.ResponseStream, shareAcl); + this.UpdateETagAndLastModified(resp); + return shareAcl; + }; + + return getCmd; + } + + /// + /// Implementation for the GetStats method. + /// + /// A object that specifies additional options for the request. + /// + private RESTCommand GetStatsImpl(FileRequestOptions options) + { + RESTCommand retCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); + options.ApplyToStorageCommand(retCmd); + retCmd.CommandLocationMode = CommandLocationMode.PrimaryOrSecondary; + retCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => ShareHttpWebRequestFactory.GetStats(uri, serverTimeout, useVersionHeader, ctx); + retCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; + retCmd.RetrieveResponseStream = true; + retCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, null /* retVal */, cmd, ex); + retCmd.PostProcessResponse = (cmd, resp, ctx) => ShareHttpResponseParsers.ReadShareStats(cmd.ResponseStream); + return retCmd; + } + /// /// Implementation for the SetMetadata method. /// @@ -1150,6 +1651,63 @@ private RESTCommand SetMetadataImpl(AccessCondition accessCondition, F return putCmd; } + /// + /// Implementation for the SetPermissions method. + /// + /// The permissions to set. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// A that sets the permissions. + private RESTCommand SetPermissionsImpl(FileSharePermissions acl, AccessCondition accessCondition, FileRequestOptions options) + { + MultiBufferMemoryStream memoryStream = new MultiBufferMemoryStream(null /* bufferManager */, (int)(1 * Constants.KB)); + FileRequest.WriteSharedAccessIdentifiers(acl.SharedAccessPolicies, memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => ShareHttpWebRequestFactory.SetAcl(uri, serverTimeout, FileSharePublicAccessType.Off, accessCondition, useVersionHeader, ctx); + putCmd.SendStream = memoryStream; + putCmd.StreamToDispose = memoryStream; + putCmd.RecoveryAction = RecoveryActions.RewindStream; + putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); + this.UpdateETagAndLastModified(resp); + return NullType.Value; + }; + + return putCmd; + } + + /// + /// Implementation for the SetProperties method. + /// + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// + /// A that sets the properties. + /// + private RESTCommand SetPropertiesImpl(AccessCondition accessCondition, FileRequestOptions options) + { + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => ShareHttpWebRequestFactory.SetProperties(uri, serverTimeout, this.Properties, accessCondition, useVersionHeader, ctx); + putCmd.SetHeaders = (r, ctx) => ShareHttpWebRequestFactory.AddMetadata(r, this.Metadata); + putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); + this.UpdateETagAndLastModified(resp); + return NullType.Value; + }; + + return putCmd; + } + /// /// Retrieve ETag and LastModified date time from response. /// diff --git a/Lib/ClassLibraryCommon/File/FileWriteStream.cs b/Lib/ClassLibraryCommon/File/FileWriteStream.cs index 931356424..798222c58 100644 --- a/Lib/ClassLibraryCommon/File/FileWriteStream.cs +++ b/Lib/ClassLibraryCommon/File/FileWriteStream.cs @@ -516,6 +516,8 @@ private void WriteRange(Stream rangeData, long offset, string contentMD5, Storag catch (Exception e) { this.lastException = e; + this.noPendingWritesEvent.Decrement(); + this.parallelOperationSemaphore.Release(); } finally { diff --git a/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpResponseParsers.cs b/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpResponseParsers.cs index b33bc4568..11fcd85dc 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpResponseParsers.cs @@ -19,10 +19,11 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol { using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System.Collections.Generic; using System.Net; /// - /// Provides a set of methods for parsing directory responses from the File service. + /// Provides methods for parsing responses to operations on directories in the File service. /// public static partial class DirectoryHttpResponseParsers { @@ -56,5 +57,15 @@ public static FileDirectoryProperties GetProperties(HttpWebResponse response) return directoryProperties; } + + /// + /// Gets the user-defined metadata. + /// + /// The response from server. + /// A of the metadata. + public static IDictionary GetMetadata(HttpWebResponse response) + { + return HttpResponseParsers.GetMetadata(response); + } } } diff --git a/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpWebRequestFactory.cs index a24f4025e..c0a6ce931 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/DirectoryHttpWebRequestFactory.cs @@ -21,19 +21,41 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; + using System.Collections.Generic; using System.Net; /// - /// A factory class for constructing a web request to manage directories in the File service. + /// A factory class for constructing web requests for operations on directories in the File service. /// public static class DirectoryHttpWebRequestFactory { + /// + /// Adds user-defined metadata to the request as one or more name-value pairs. + /// + /// The web request. + /// The user-defined metadata. + public static void AddMetadata(HttpWebRequest request, IDictionary metadata) + { + HttpWebRequestFactory.AddMetadata(request, metadata); + } + + /// + /// Adds user-defined metadata to the request as a single name-value pair. + /// + /// The web request. + /// The metadata name. + /// The metadata value. + public static void AddMetadata(HttpWebRequest request, string name, string value) + { + HttpWebRequestFactory.AddMetadata(request, name, value); + } + /// /// Constructs a web request to create a new directory. /// /// The absolute URI to the directory. /// The server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. public static HttpWebRequest Create(Uri uri, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -48,9 +70,9 @@ public static HttpWebRequest Create(Uri uri, int? timeout, bool useVersionHeader /// The absolute URI to the directory. /// The server timeout interval. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. - /// A web request to use to perform the operation. + /// A object. public static HttpWebRequest Delete(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); @@ -65,45 +87,81 @@ public static HttpWebRequest Delete(Uri uri, int? timeout, AccessCondition acces /// The absolute URI to the directory. /// The server timeout interval. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. - /// A web request to use to perform the operation. + /// A object. public static HttpWebRequest GetProperties(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); + HttpWebRequest request = HttpWebRequestFactory.GetProperties(uri, timeout, directoryBuilder, useVersionHeader, operationContext); request.ApplyAccessCondition(accessCondition); return request; } + /// + /// Generates a web request to return the user-defined metadata for this directory. + /// + /// The absolute URI to the directory. + /// The server timeout interval. + /// The access condition to apply to the request. + /// A boolean value indicating whether to set the x-ms-version HTTP header. + /// An object for tracking the current operation. + /// A object. + public static HttpWebRequest GetMetadata(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + { + UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); + + HttpWebRequest request = HttpWebRequestFactory.GetMetadata(uri, timeout, directoryBuilder, useVersionHeader, operationContext); + request.ApplyAccessCondition(accessCondition); + return request; + } + /// /// Generates a web request to return a listing of all files and subdirectories in the directory. /// /// The absolute URI to the share. /// The server timeout interval. /// A set of parameters for the listing operation. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. - /// A web request to use to perform the operation. + /// A object. public static HttpWebRequest List(Uri uri, int? timeout, FileListingContext listingContext, bool useVersionHeader, OperationContext operationContext) { - UriQueryBuilder builder = GetDirectoryUriQueryBuilder(); - builder.Add(Constants.QueryConstants.Component, "list"); + UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); + directoryBuilder.Add(Constants.QueryConstants.Component, "list"); if (listingContext != null) { if (listingContext.Marker != null) { - builder.Add("marker", listingContext.Marker); + directoryBuilder.Add("marker", listingContext.Marker); } if (listingContext.MaxResults.HasValue) { - builder.Add("maxresults", listingContext.MaxResults.ToString()); + directoryBuilder.Add("maxresults", listingContext.MaxResults.ToString()); } } - HttpWebRequest request = HttpWebRequestFactory.CreateWebRequest(WebRequestMethods.Http.Get, uri, timeout, builder, useVersionHeader, operationContext); + HttpWebRequest request = HttpWebRequestFactory.CreateWebRequest(WebRequestMethods.Http.Get, uri, timeout, directoryBuilder, useVersionHeader, operationContext); + return request; + } + + /// + /// Constructs a web request to set user-defined metadata for the directory. + /// + /// A specifying the absolute URI to the destination blob. + /// An integer specifying the server timeout interval. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A boolean value indicating whether to set the x-ms-version HTTP header. + /// An object for tracking the current operation. + /// A web request for performing the operation. + public static HttpWebRequest SetMetadata(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + { + UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); + HttpWebRequest request = HttpWebRequestFactory.SetMetadata(uri, timeout, directoryBuilder, useVersionHeader, operationContext); + request.ApplyAccessCondition(accessCondition); return request; } diff --git a/Lib/ClassLibraryCommon/File/Protocol/FileHttpResponseParsers.cs b/Lib/ClassLibraryCommon/File/Protocol/FileHttpResponseParsers.cs index c17f77f45..e0c36e2cc 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/FileHttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/FileHttpResponseParsers.cs @@ -20,14 +20,17 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol { + using Microsoft.WindowsAzure.Storage.Blob; + using Microsoft.WindowsAzure.Storage.Blob.Protocol; using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; using System.Collections.Generic; using System.Globalization; using System.Net; /// - /// Provides a set of methods for parsing a response containing file data from the File service. + /// Provides methods for parsing responses to operations on files in the File service. /// public static partial class FileHttpResponseParsers { @@ -102,5 +105,15 @@ public static IDictionary GetMetadata(HttpWebResponse response) { return HttpResponseParsers.GetMetadata(response); } + + /// + /// Extracts a object from the headers of a web response. + /// + /// The HTTP web response. + /// A object, or null if the web response does not include copy state. + public static CopyState GetCopyAttributes(HttpWebResponse response) + { + return BlobHttpResponseParsers.GetCopyAttributes(response); + } } } diff --git a/Lib/ClassLibraryCommon/File/Protocol/FileHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/File/Protocol/FileHttpWebRequestFactory.cs index 8142ccca0..f3dd0e72d 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/FileHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/FileHttpWebRequestFactory.cs @@ -23,13 +23,54 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol using System; using System.Collections.Generic; using System.Globalization; + using System.IO; using System.Net; /// - /// A factory class for constructing a web request to manage files in the File service. + /// A factory class for constructing web requests for operations on files in the File service. /// public static class FileHttpWebRequestFactory { + /// + /// Creates a web request to get the properties of the File service. + /// + /// A specifying the File service endpoint. + /// A object specifying additional parameters to add to the URI query string. + /// The server timeout interval, in seconds. + /// A boolean value indicating whether to set the x-ms-version HTTP header. + /// An object that represents the context for the current operation. + /// A object. + public static HttpWebRequest GetServiceProperties(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) + { + return HttpWebRequestFactory.GetServiceProperties(uri, builder, timeout, useVersionHeader, operationContext); + } + + /// + /// Creates a web request to set the properties of the File service. + /// + /// A specifying the File service endpoint. + /// A object specifying additional parameters to add to the URI query string. + /// The server timeout interval, in seconds. + /// A boolean value indicating whether to set the x-ms-version HTTP header. + /// An object that represents the context for the current operation. + /// A object. + public static HttpWebRequest SetServiceProperties(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) + { + return HttpWebRequestFactory.SetServiceProperties(uri, builder, timeout, useVersionHeader, operationContext); + } + + /// + /// Writes File service properties to a stream, formatted in XML. + /// + /// A object. + /// The object to which the formatted properties are to be written. + public static void WriteServiceProperties(FileServiceProperties properties, Stream outputStream) + { + CommonUtility.AssertNotNull("properties", properties); + + properties.WriteServiceProperties(outputStream); + } + /// /// Constructs a web request to create a new file. /// @@ -38,9 +79,9 @@ public static class FileHttpWebRequestFactory /// The properties to set for the file. /// The size of the file. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. - /// A web request to use to perform the operation. + /// A object. public static HttpWebRequest Create(Uri uri, int? timeout, FileProperties properties, long fileSize, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { CommonUtility.AssertNotNull("properties", properties); @@ -93,12 +134,14 @@ public static HttpWebRequest Create(Uri uri, int? timeout, FileProperties proper /// The absolute URI to the file. /// The server timeout interval. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request for performing the operation. public static HttpWebRequest GetProperties(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { - HttpWebRequest request = HttpWebRequestFactory.GetProperties(uri, timeout, null /* builder */, useVersionHeader, operationContext); + UriQueryBuilder builder = new UriQueryBuilder(); + + HttpWebRequest request = HttpWebRequestFactory.GetProperties(uri, timeout, builder, useVersionHeader, operationContext); request.ApplyAccessCondition(accessCondition); return request; } @@ -109,12 +152,14 @@ public static HttpWebRequest GetProperties(Uri uri, int? timeout, AccessConditio /// The absolute URI to the file. /// The server timeout interval. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request for performing the operation. public static HttpWebRequest GetMetadata(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { - HttpWebRequest request = HttpWebRequestFactory.GetMetadata(uri, timeout, null /* builder */, useVersionHeader, operationContext); + UriQueryBuilder builder = new UriQueryBuilder(); + + HttpWebRequest request = HttpWebRequestFactory.GetMetadata(uri, timeout, builder, useVersionHeader, operationContext); request.ApplyAccessCondition(accessCondition); return request; } @@ -146,9 +191,9 @@ public static void AddMetadata(HttpWebRequest request, string name, string value /// The absolute URI to the file. /// The server timeout interval. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. - /// A web request to use to perform the operation. + /// A object. public static HttpWebRequest Delete(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { HttpWebRequest request = HttpWebRequestFactory.Delete(uri, null /* builder */, timeout, useVersionHeader, operationContext); @@ -192,9 +237,9 @@ private static void AddRange(HttpWebRequest request, long? offset, long? count) /// The starting offset of the data range over which to list file ranges, in bytes. /// The length of the data range over which to list file ranges, in bytes. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. - /// A web request to use to perform the operation. + /// A object. public static HttpWebRequest ListRanges(Uri uri, int? timeout, long? offset, long? count, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { if (offset.HasValue) @@ -218,9 +263,9 @@ public static HttpWebRequest ListRanges(Uri uri, int? timeout, long? offset, lon /// The server timeout interval. /// The file's properties. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. - /// A web request to use to perform the operation. + /// A object. public static HttpWebRequest SetProperties(Uri uri, int? timeout, FileProperties properties, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { CommonUtility.AssertNotNull("properties", properties); @@ -251,7 +296,7 @@ public static HttpWebRequest SetProperties(Uri uri, int? timeout, FileProperties /// The server timeout interval. /// The new file size. Set this parameter to null to keep the existing file size. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. public static HttpWebRequest Resize(Uri uri, int? timeout, long newFileSize, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -273,12 +318,14 @@ public static HttpWebRequest Resize(Uri uri, int? timeout, long newFileSize, Acc /// The absolute URI to the file. /// The server timeout interval. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request for performing the operation. public static HttpWebRequest Get(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { - HttpWebRequest request = HttpWebRequestFactory.CreateWebRequest(WebRequestMethods.Http.Get, uri, timeout, null /* builder */, useVersionHeader, operationContext); + UriQueryBuilder builder = new UriQueryBuilder(); + + HttpWebRequest request = HttpWebRequestFactory.CreateWebRequest(WebRequestMethods.Http.Get, uri, timeout, builder, useVersionHeader, operationContext); request.ApplyAccessCondition(accessCondition); return request; } @@ -289,7 +336,7 @@ public static HttpWebRequest Get(Uri uri, int? timeout, AccessCondition accessCo /// The absolute URI to the file. /// The server timeout interval. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request for performing the operation. public static HttpWebRequest SetMetadata(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -308,7 +355,7 @@ public static HttpWebRequest SetMetadata(Uri uri, int? timeout, AccessCondition /// The number of bytes to return, or null to return all bytes through the end of the file. /// If set to true, request an MD5 header for the specified range. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. public static HttpWebRequest Get(Uri uri, int? timeout, long? offset, long? count, bool rangeContentMD5, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -343,7 +390,7 @@ public static HttpWebRequest Get(Uri uri, int? timeout, long? offset, long? coun /// The beginning and ending offsets. /// Action describing whether we are writing to a file or clearing a set of ranges. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A web request to use to perform the operation. public static HttpWebRequest PutRange(Uri uri, int? timeout, FileRange fileRange, FileRangeWrite fileRangeWrite, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -361,5 +408,53 @@ public static HttpWebRequest PutRange(Uri uri, int? timeout, FileRange fileRange request.ApplyAccessCondition(accessCondition); return request; } + + /// + /// Generates a web request to copy from a blob or file to another file. + /// + /// A specifying the absolute URI to the destination file. + /// An integer specifying the server timeout interval. + /// A specifying the absolute URI to the source object, including any necessary authentication parameters. + /// An object that represents the condition that must be met on the source object in order for the request to proceed. + /// An object that represents the condition that must be met on the destination file in order for the request to proceed. + /// A boolean value indicating whether to set the x-ms-version HTTP header. + /// An object that represents the context for the current operation. + /// A object. + public static HttpWebRequest CopyFrom(Uri uri, int? timeout, Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, bool useVersionHeader, OperationContext operationContext) + { + CommonUtility.AssertNotNull("source", source); + + HttpWebRequest request = HttpWebRequestFactory.CreateWebRequest(WebRequestMethods.Http.Put, uri, timeout, null /* builder */, useVersionHeader, operationContext); + + request.Headers.Add(Constants.HeaderConstants.CopySourceHeader, source.AbsoluteUri); + request.ApplyAccessCondition(destAccessCondition); + request.ApplyAccessConditionToSource(sourceAccessCondition); + + return request; + } + + /// + /// Generates a web request to abort a copy operation. + /// + /// A specifying the absolute URI to the file. + /// An integer specifying the server timeout interval. + /// The ID string of the copy operation to be aborted. + /// An object that represents the condition that must be met in order for the request to proceed. Only lease conditions are supported for this operation. + /// A boolean value indicating whether to set the x-ms-version HTTP header. + /// An object that represents the context for the current operation. + /// A object. + public static HttpWebRequest AbortCopy(Uri uri, int? timeout, string copyId, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + { + UriQueryBuilder builder = new UriQueryBuilder(); + builder.Add(Constants.QueryConstants.Component, "copy"); + builder.Add(Constants.QueryConstants.CopyId, copyId); + + HttpWebRequest request = HttpWebRequestFactory.CreateWebRequest(WebRequestMethods.Http.Put, uri, timeout, builder, useVersionHeader, operationContext); + + request.Headers.Add(Constants.HeaderConstants.CopyActionHeader, Constants.HeaderConstants.CopyActionAbort); + request.ApplyAccessCondition(accessCondition); + + return request; + } } } diff --git a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpResponseParsers.cs b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpResponseParsers.cs index ba8da528e..89281eaab 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpResponseParsers.cs @@ -17,13 +17,20 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol { + using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; using System.Net; + using System.Xml; + using System.Xml.Linq; /// - /// Provides a set of methods for parsing share responses from the File service. + /// Provides methods for parsing responses to operations on shares in the File service. /// public static partial class ShareHttpResponseParsers { @@ -56,6 +63,12 @@ public static FileShareProperties GetProperties(HttpWebResponse response) shareProperties.LastModified = response.LastModified.ToUniversalTime(); #endif + string quota = response.Headers[Constants.HeaderConstants.ShareQuota]; + if (!string.IsNullOrEmpty(quota)) + { + shareProperties.Quota = int.Parse(quota, CultureInfo.InvariantCulture); + } + return shareProperties; } @@ -68,5 +81,32 @@ public static IDictionary GetMetadata(HttpWebResponse response) { return HttpResponseParsers.GetMetadata(response); } + + /// + /// Reads the share access policies from a stream in XML. + /// + /// The stream of XML policies. + /// The permissions object to which the policies are to be written. + public static void ReadSharedAccessIdentifiers(Stream inputStream, FileSharePermissions permissions) + { + CommonUtility.AssertNotNull("permissions", permissions); + + Response.ReadSharedAccessIdentifiers(permissions.SharedAccessPolicies, new FileAccessPolicyResponse(inputStream)); + } + + /// + /// Reads share stats from a stream. + /// + /// The stream from which to read the share stats. + /// The share stats stored in the stream. + public static ShareStats ReadShareStats(Stream inputStream) + { + using (XmlReader reader = XmlReader.Create(inputStream)) + { + XDocument shareStatsDocument = XDocument.Load(reader); + + return ShareStats.FromServiceXml(shareStatsDocument); + } + } } } diff --git a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs index 5f16f6aa6..190143b18 100644 --- a/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/File/Protocol/ShareHttpWebRequestFactory.cs @@ -19,13 +19,15 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol { using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Collections.Generic; using System.Net; + using System.Text; /// - /// A factory class for constructing a web request to manage shares in the File service. + /// A factory class for constructing web requests for operations on shares in the File service. /// public static class ShareHttpWebRequestFactory { @@ -34,13 +36,34 @@ public static class ShareHttpWebRequestFactory /// /// The absolute URI to the share. /// The server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. public static HttpWebRequest Create(Uri uri, int? timeout, bool useVersionHeader, OperationContext operationContext) + { + return ShareHttpWebRequestFactory.Create(uri, null /* properties */, timeout, useVersionHeader, operationContext); + } + + /// + /// Constructs a web request to create a new share. + /// + /// The absolute URI to the share. + /// Properties to set on the share. + /// The server timeout interval. + /// A boolean value indicating whether to set the x-ms-version HTTP header. + /// An object for tracking the current operation. + /// A web request to use to perform the operation. + public static HttpWebRequest Create(Uri uri, FileShareProperties properties, int? timeout, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); - return HttpWebRequestFactory.Create(uri, timeout, shareBuilder, useVersionHeader, operationContext); + + HttpWebRequest request = HttpWebRequestFactory.Create(uri, timeout, shareBuilder, useVersionHeader, operationContext); + if (properties != null && properties.Quota.HasValue) + { + request.AddOptionalHeader(Constants.HeaderConstants.ShareQuota, properties.Quota); + } + + return request; } /// @@ -49,12 +72,13 @@ public static HttpWebRequest Create(Uri uri, int? timeout, bool useVersionHeader /// The absolute URI to the share. /// The server timeout interval. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. public static HttpWebRequest Delete(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); + HttpWebRequest request = HttpWebRequestFactory.Delete(uri, shareBuilder, timeout, useVersionHeader, operationContext); request.ApplyAccessCondition(accessCondition); return request; @@ -66,12 +90,13 @@ public static HttpWebRequest Delete(Uri uri, int? timeout, AccessCondition acces /// The absolute URI to the share. /// The server timeout interval. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. public static HttpWebRequest GetMetadata(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); + HttpWebRequest request = HttpWebRequestFactory.GetMetadata(uri, timeout, shareBuilder, useVersionHeader, operationContext); request.ApplyAccessCondition(accessCondition); return request; @@ -83,12 +108,13 @@ public static HttpWebRequest GetMetadata(Uri uri, int? timeout, AccessCondition /// The absolute URI to the share. /// The server timeout interval. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. public static HttpWebRequest GetProperties(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); + HttpWebRequest request = HttpWebRequestFactory.GetProperties(uri, timeout, shareBuilder, useVersionHeader, operationContext); request.ApplyAccessCondition(accessCondition); return request; @@ -100,7 +126,7 @@ public static HttpWebRequest GetProperties(Uri uri, int? timeout, AccessConditio /// The absolute URI to the share. /// The server timeout interval. /// The access condition to apply to the request. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request to use to perform the operation. public static HttpWebRequest SetMetadata(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) @@ -111,6 +137,52 @@ public static HttpWebRequest SetMetadata(Uri uri, int? timeout, AccessCondition return request; } + /// + /// Constructs a web request to set system properties for a share. + /// + /// A specifying the absolute URI to the share. + /// An integer specifying the server timeout interval. + /// The share's properties. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A boolean value indicating whether to set the x-ms-version HTTP header. + /// An object that represents the context for the current operation. + /// A object. + public static HttpWebRequest SetProperties(Uri uri, int? timeout, FileShareProperties properties, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + { + CommonUtility.AssertNotNull("properties", properties); + + UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); + shareBuilder.Add(Constants.QueryConstants.Component, "properties"); + + HttpWebRequest request = HttpWebRequestFactory.CreateWebRequest(WebRequestMethods.Http.Put, uri, timeout, shareBuilder, useVersionHeader, operationContext); + + if (properties.Quota.HasValue) + { + request.AddOptionalHeader(Constants.HeaderConstants.ShareQuota, properties.Quota.Value); + } + + request.ApplyAccessCondition(accessCondition); + return request; + } + + /// + /// Creates a web request to get the stats of the share. + /// + /// A specifying the share. + /// The server timeout interval, in seconds. + /// A boolean value indicating whether to set the x-ms-version HTTP header. + /// An object that represents the context for the current operation. + /// A object. + public static HttpWebRequest GetStats(Uri uri, int? timeout, bool useVersionHeader, OperationContext operationContext) + { + UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); + shareBuilder.Add(Constants.QueryConstants.Component, "stats"); + + HttpWebRequest request = HttpWebRequestFactory.CreateWebRequest(WebRequestMethods.Http.Get, uri, timeout, shareBuilder, useVersionHeader, operationContext); + + return request; + } + /// /// Adds user-defined metadata to the request as one or more name-value pairs. /// @@ -139,7 +211,7 @@ public static void AddMetadata(HttpWebRequest request, string name, string value /// The server timeout interval. /// A set of parameters for the listing operation. /// Additional details to return with the listing. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object for tracking the current operation. /// A web request for the specified operation. public static HttpWebRequest List(Uri uri, int? timeout, ListingContext listingContext, ShareListingDetails detailsIncluded, bool useVersionHeader, OperationContext operationContext) @@ -174,6 +246,40 @@ public static HttpWebRequest List(Uri uri, int? timeout, ListingContext listingC return request; } + /// + /// Constructs a web request to return the ACL for a share. + /// + /// A specifying the absolute URI to the share. + /// An integer specifying the server timeout interval. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A boolean value indicating whether to set the x-ms-version HTTP header. + /// An object that represents the context for the current operation. + /// A object. + public static HttpWebRequest GetAcl(Uri uri, int? timeout, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + { + HttpWebRequest request = HttpWebRequestFactory.GetAcl(uri, GetShareUriQueryBuilder(), timeout, useVersionHeader, operationContext); + request.ApplyAccessCondition(accessCondition); + return request; + } + + /// + /// Constructs a web request to set the ACL for a share. + /// + /// A specifying the absolute URI to the share. + /// An integer specifying the server timeout interval. + /// The type of public access to allow for the share. + /// An object that represents the condition that must be met in order for the request to proceed. + /// A boolean value indicating whether to set the x-ms-version HTTP header. + /// An object that represents the context for the current operation. + /// A object. + public static HttpWebRequest SetAcl(Uri uri, int? timeout, FileSharePublicAccessType publicAccess, AccessCondition accessCondition, bool useVersionHeader, OperationContext operationContext) + { + HttpWebRequest request = HttpWebRequestFactory.SetAcl(uri, GetShareUriQueryBuilder(), timeout, useVersionHeader, operationContext); + + request.ApplyAccessCondition(accessCondition); + return request; + } + /// /// Gets the share Uri query builder. /// diff --git a/Lib/ClassLibraryCommon/Queue/CloudQueue.cs b/Lib/ClassLibraryCommon/Queue/CloudQueue.cs index c78072252..d0f289513 100644 --- a/Lib/ClassLibraryCommon/Queue/CloudQueue.cs +++ b/Lib/ClassLibraryCommon/Queue/CloudQueue.cs @@ -2504,6 +2504,7 @@ private RESTCommand SetPermissionsImpl(QueuePermissions acl, QueueRequ options.ApplyToStorageCommand(putCmd); putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => QueueHttpWebRequestFactory.SetAcl(uri, serverTimeout, useVersionHeader, ctx); putCmd.SendStream = memoryStream; + putCmd.StreamToDispose = memoryStream; putCmd.RecoveryAction = RecoveryActions.RewindStream; putCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => @@ -2576,6 +2577,7 @@ private RESTCommand AddMessageImpl(CloudQueueMessage message, TimeSpan options.ApplyToStorageCommand(putCmd); putCmd.BuildRequestDelegate = (uri, builder, serverTimeout, useVersionHeader, ctx) => QueueHttpWebRequestFactory.AddMessage(uri, serverTimeout, timeToLiveInSeconds, initialVisibilityDelayInSeconds, useVersionHeader, ctx); putCmd.SendStream = memoryStream; + putCmd.StreamToDispose = memoryStream; putCmd.RecoveryAction = RecoveryActions.RewindStream; putCmd.SetHeaders = (r, ctx) => { @@ -2641,6 +2643,7 @@ private RESTCommand UpdateMessageImpl(CloudQueueMessage message, TimeS memoryStream.Seek(0, SeekOrigin.Begin); putCmd.SendStream = memoryStream; + putCmd.StreamToDispose = memoryStream; putCmd.RecoveryAction = RecoveryActions.RewindStream; } @@ -2684,6 +2687,8 @@ private RESTCommand DeleteMessageImpl(string messageId, string popRece /// A that gets the permissions. private RESTCommand> GetMessagesImpl(int messageCount, TimeSpan? visibilityTimeout, QueueRequestOptions options) { + options.AssertPolicyIfRequired(); + RESTCommand> getCmd = new RESTCommand>(this.ServiceClient.Credentials, this.GetMessageRequestAddress()); options.ApplyToStorageCommand(getCmd); @@ -2711,6 +2716,8 @@ private RESTCommand> GetMessagesImpl(int messageC /// A that gets the permissions. private RESTCommand> PeekMessagesImpl(int messageCount, QueueRequestOptions options) { + options.AssertPolicyIfRequired(); + RESTCommand> getCmd = new RESTCommand>(this.ServiceClient.Credentials, this.GetMessageRequestAddress()); options.ApplyToStorageCommand(getCmd); diff --git a/Lib/ClassLibraryCommon/Queue/CloudQueueClient.cs b/Lib/ClassLibraryCommon/Queue/CloudQueueClient.cs index 9c8f0607a..eb962f107 100644 --- a/Lib/ClassLibraryCommon/Queue/CloudQueueClient.cs +++ b/Lib/ClassLibraryCommon/Queue/CloudQueueClient.cs @@ -730,6 +730,7 @@ private RESTCommand SetServicePropertiesImpl(ServiceProperties propert } catch (InvalidOperationException invalidOpException) { + str.Dispose(); throw new ArgumentException(invalidOpException.Message, "properties"); } @@ -737,6 +738,7 @@ private RESTCommand SetServicePropertiesImpl(ServiceProperties propert RESTCommand retCmd = new RESTCommand(this.Credentials, this.StorageUri); retCmd.SendStream = str; + retCmd.StreamToDispose = str; retCmd.BuildRequestDelegate = QueueHttpWebRequestFactory.SetServiceProperties; retCmd.RecoveryAction = RecoveryActions.RewindStream; retCmd.SignRequest = this.AuthenticationHandler.SignRequest; diff --git a/Lib/ClassLibraryCommon/Queue/Protocol/QueueHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/Queue/Protocol/QueueHttpWebRequestFactory.cs index c525fb565..6070c4e57 100644 --- a/Lib/ClassLibraryCommon/Queue/Protocol/QueueHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/Queue/Protocol/QueueHttpWebRequestFactory.cs @@ -50,7 +50,7 @@ public static HttpWebRequest GetServiceProperties(Uri uri, UriQueryBuilder build /// A specifying the Queue service endpoint. /// A object specifying additional parameters to add to the URI query string. /// The server timeout interval, in seconds. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. internal static HttpWebRequest GetServiceProperties(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -77,7 +77,7 @@ public static HttpWebRequest SetServiceProperties(Uri uri, UriQueryBuilder build /// A specifying the Queue service endpoint. /// A object specifying additional parameters to add to the URI query string. /// The server timeout interval, in seconds. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. internal static HttpWebRequest SetServiceProperties(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -104,7 +104,7 @@ public static HttpWebRequest GetServiceStats(Uri uri, UriQueryBuilder builder, i /// A specifying the Queue service endpoint. /// A object specifying additional parameters to add to the URI query string. /// The server timeout interval, in seconds. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. internal static HttpWebRequest GetServiceStats(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -141,7 +141,7 @@ public static HttpWebRequest Create(Uri uri, int? timeout, OperationContext oper /// /// A specifying the absolute URI to the queue. /// An integer specifying the server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest Create(Uri uri, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -166,7 +166,7 @@ public static HttpWebRequest Delete(Uri uri, int? timeout, OperationContext oper /// /// A specifying the absolute URI to the queue. /// An integer specifying the server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest Delete(Uri uri, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -192,7 +192,7 @@ public static HttpWebRequest ClearMessages(Uri uri, int? timeout, OperationConte /// /// A specifying the absolute URI to the queue. /// An integer specifying the server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest ClearMessages(Uri uri, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -218,7 +218,7 @@ public static HttpWebRequest GetMetadata(Uri uri, int? timeout, OperationContext /// /// A specifying the absolute URI to the queue. /// An integer specifying the server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest GetMetadata(Uri uri, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -244,7 +244,7 @@ public static HttpWebRequest SetMetadata(Uri uri, int? timeout, OperationContext /// /// A specifying the absolute URI to the queue. /// An integer specifying the server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest SetMetadata(Uri uri, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -295,7 +295,7 @@ public static HttpWebRequest List(Uri uri, int? timeout, ListingContext listingC /// An integer specifying the server timeout interval. /// A object. /// A enumeration value that indicates whether to return queue metadata with the listing. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest List(Uri uri, int? timeout, ListingContext listingContext, QueueListingDetails detailsIncluded, bool useVersionHeader, OperationContext operationContext) @@ -347,7 +347,7 @@ public static HttpWebRequest GetAcl(Uri uri, int? timeout, OperationContext oper /// /// A specifying the absolute URI to the queue. /// An integer specifying the server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest GetAcl(Uri uri, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -373,7 +373,7 @@ public static HttpWebRequest SetAcl(Uri uri, int? timeout, OperationContext oper /// /// A specifying the absolute URI to the queue. /// An integer specifying the server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest SetAcl(Uri uri, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -403,7 +403,7 @@ public static HttpWebRequest AddMessage(Uri uri, int? timeout, int? timeToLiveIn /// An integer specifying the server timeout interval. /// The message time-to-live, in seconds. /// The length of time during which the message will be invisible, in seconds. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest AddMessage(Uri uri, int? timeout, int? timeToLiveInSeconds, int? visibilityTimeoutInSeconds, bool useVersionHeader, OperationContext operationContext) @@ -445,7 +445,7 @@ public static HttpWebRequest UpdateMessage(Uri uri, int? timeout, string popRece /// The server timeout interval, in seconds. /// A string specifying the pop receipt of the message. /// The length of time during which the message will be invisible, in seconds. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest UpdateMessage(Uri uri, int? timeout, string popReceipt, int visibilityTimeoutInSeconds, bool useVersionHeader, OperationContext operationContext) @@ -478,7 +478,7 @@ public static HttpWebRequest DeleteMessage(Uri uri, int? timeout, string popRece /// A specifying the absolute URI to the message to update. /// The server timeout interval, in seconds. /// A string specifying the pop receipt of the message. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest DeleteMessage(Uri uri, int? timeout, string popReceipt, bool useVersionHeader, OperationContext operationContext) @@ -511,7 +511,7 @@ public static HttpWebRequest GetMessages(Uri uri, int? timeout, int numberOfMess /// An integer specifying the server timeout interval. /// An integer specifying the number of messages to get. /// A value specifying the visibility timeout. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest GetMessages(Uri uri, int? timeout, int numberOfMessages, TimeSpan? visibilityTimeout, bool useVersionHeader, OperationContext operationContext) @@ -548,7 +548,7 @@ public static HttpWebRequest PeekMessages(Uri uri, int? timeout, int numberOfMes /// A specifying the absolute URI to the queue. /// An integer specifying the server timeout interval. /// An integer specifying the number of messages to peek. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest PeekMessages(Uri uri, int? timeout, int numberOfMessages, bool useVersionHeader, OperationContext operationContext) diff --git a/Lib/ClassLibraryCommon/Queue/QueueEncryptionPolicy.cs b/Lib/ClassLibraryCommon/Queue/QueueEncryptionPolicy.cs index ee97034f6..11b2d0d59 100644 --- a/Lib/ClassLibraryCommon/Queue/QueueEncryptionPolicy.cs +++ b/Lib/ClassLibraryCommon/Queue/QueueEncryptionPolicy.cs @@ -29,7 +29,7 @@ namespace Microsoft.WindowsAzure.Storage.Queue using System.Threading; /// - /// Represents a queue encryption policy that is used to perform envelope encryption/decryption of Azure queue messages. + /// Represents an encryption policy for performing envelope encryption/decryption of messages in Azure queue. /// public sealed class QueueEncryptionPolicy { @@ -39,21 +39,21 @@ public sealed class QueueEncryptionPolicy public IKey Key { get; private set; } /// - /// Gets or sets the key resolver used to select the correct key for decrypting existing queue mesaages. + /// Gets or sets the key resolver used to select the correct key for decrypting existing queue messages. /// - /// A resolver that returns an given a keyId. + /// A resolver that returns an , given a key ID. public IKeyResolver KeyResolver { get; private set; } /// /// Initializes a new instance of the class with the specified key and resolver. /// /// An object of type that is used to wrap/unwrap the content encryption key. - /// The key resolver used to select the correct key for decrypting existing queue mesaages. - /// If the generated policy is intended to be used for encryption, users are expected to provide a key at the minimum. - /// The absence of key will cause an exception to be thrown during encryption. - /// If the generated policy is intended to be used for decryption, users can provide a keyResolver. The client library will - - /// 1. Invoke the key resolver if specified to get the key. - /// 2. If resolver is not specified but a key is specified, match the key id on the key and use it. + /// The key resolver used to select the correct key for decrypting existing queue messages. + /// If the generated policy is to be used for encryption, users are expected to provide a key at the minimum. + /// The absence of key will cause an exception to be thrown during encryption.
+ /// If the generated policy is intended to be used for decryption, users can provide a key resolver. The client library will:
+ /// 1. Invoke the key resolver, if specified, to get the key.
+ /// 2. If resolver is not specified but a key is specified, the client library will match the key ID against the key and use the key.
public QueueEncryptionPolicy(IKey key, IKeyResolver keyResolver) { this.Key = key; @@ -105,81 +105,93 @@ internal string EncryptMessage(byte[] inputMessage) /// Returns a plain text message given an encrypted message. /// /// The encrypted message. + /// A value to indicate that the data read from the server should be encrypted. /// The plain text message bytes. - internal byte[] DecryptMessage(string inputMessage) + internal byte[] DecryptMessage(string inputMessage, bool? requireEncryption) { CommonUtility.AssertNotNull("inputMessage", inputMessage); try { CloudQueueEncryptedMessage encryptedMessage = JsonConvert.DeserializeObject(inputMessage); - - CommonUtility.AssertNotNull("EncryptionData", encryptedMessage.EncryptionData); - EncryptionData encryptionData = encryptedMessage.EncryptionData; - - CommonUtility.AssertNotNull("ContentEncryptionIV", encryptionData.ContentEncryptionIV); - CommonUtility.AssertNotNull("EncryptedKey", encryptionData.WrappedContentKey.EncryptedKey); - - // Throw if the encryption protocol on the message doesn't match the version that this client library understands - // and is able to decrypt. - if (encryptionData.EncryptionAgent.Protocol != Constants.EncryptionConstants.EncryptionProtocolV1) + + if (requireEncryption.HasValue && requireEncryption.Value && encryptedMessage.EncryptionData == null) { - throw new StorageException(SR.EncryptionProtocolVersionInvalid, null) { IsRetryable = false }; + throw new StorageException(SR.EncryptionDataNotPresentError, null) { IsRetryable = false }; } - // Throw if neither the key nor the key resolver are set. - if (this.Key == null && this.KeyResolver == null) + if (encryptedMessage.EncryptionData != null) { - throw new StorageException(SR.KeyAndResolverMissingError, null) { IsRetryable = false }; - } + EncryptionData encryptionData = encryptedMessage.EncryptionData; - byte[] contentEncryptionKey = null; + CommonUtility.AssertNotNull("ContentEncryptionIV", encryptionData.ContentEncryptionIV); + CommonUtility.AssertNotNull("EncryptedKey", encryptionData.WrappedContentKey.EncryptedKey); - // 1. Invoke the key resolver if specified to get the key. If the resolver is specified but does not have a - // mapping for the key id, an error should be thrown. This is important for key rotation scenario. - // 2. If resolver is not specified but a key is specified, match the key id on the key and and use it. - // Calling UnwrapKeyAsync synchronously is fine because for the storage client scenario, unwrap happens - // locally. No service call is made. - if (this.KeyResolver != null) - { - IKey keyEncryptionKey = this.KeyResolver.ResolveKeyAsync(encryptionData.WrappedContentKey.KeyId, CancellationToken.None).Result; + // Throw if the encryption protocol on the message doesn't match the version that this client library understands + // and is able to decrypt. + if (encryptionData.EncryptionAgent.Protocol != Constants.EncryptionConstants.EncryptionProtocolV1) + { + throw new StorageException(SR.EncryptionProtocolVersionInvalid, null) { IsRetryable = false }; + } - CommonUtility.AssertNotNull("keyEncryptionKey", keyEncryptionKey); - contentEncryptionKey = keyEncryptionKey.UnwrapKeyAsync(encryptionData.WrappedContentKey.EncryptedKey, encryptionData.WrappedContentKey.Algorithm, CancellationToken.None).Result; - } - else - { - if (this.Key.Kid == encryptionData.WrappedContentKey.KeyId) + // Throw if neither the key nor the key resolver are set. + if (this.Key == null && this.KeyResolver == null) { - contentEncryptionKey = this.Key.UnwrapKeyAsync(encryptionData.WrappedContentKey.EncryptedKey, encryptionData.WrappedContentKey.Algorithm, CancellationToken.None).Result; + throw new StorageException(SR.KeyAndResolverMissingError, null) { IsRetryable = false }; + } + + byte[] contentEncryptionKey = null; + + // 1. Invoke the key resolver if specified to get the key. If the resolver is specified but does not have a + // mapping for the key id, an error should be thrown. This is important for key rotation scenario. + // 2. If resolver is not specified but a key is specified, match the key id on the key and and use it. + // Calling UnwrapKeyAsync synchronously is fine because for the storage client scenario, unwrap happens + // locally. No service call is made. + if (this.KeyResolver != null) + { + IKey keyEncryptionKey = this.KeyResolver.ResolveKeyAsync(encryptionData.WrappedContentKey.KeyId, CancellationToken.None).Result; + + CommonUtility.AssertNotNull("keyEncryptionKey", keyEncryptionKey); + contentEncryptionKey = keyEncryptionKey.UnwrapKeyAsync(encryptionData.WrappedContentKey.EncryptedKey, encryptionData.WrappedContentKey.Algorithm, CancellationToken.None).Result; } else { - throw new StorageException(SR.KeyMismatch, null) { IsRetryable = false }; + if (this.Key.Kid == encryptionData.WrappedContentKey.KeyId) + { + contentEncryptionKey = this.Key.UnwrapKeyAsync(encryptionData.WrappedContentKey.EncryptedKey, encryptionData.WrappedContentKey.Algorithm, CancellationToken.None).Result; + } + else + { + throw new StorageException(SR.KeyMismatch, null) { IsRetryable = false }; + } } - } - switch (encryptionData.EncryptionAgent.EncryptionAlgorithm) - { - case EncryptionAlgorithm.AES_CBC_256: + switch (encryptionData.EncryptionAgent.EncryptionAlgorithm) + { + case EncryptionAlgorithm.AES_CBC_256: #if WINDOWS_DESKTOP && !WINDOWS_PHONE - using (AesCryptoServiceProvider myAes = new AesCryptoServiceProvider()) + using (AesCryptoServiceProvider myAes = new AesCryptoServiceProvider()) #else using (AesManaged myAes = new AesManaged()) #endif - { - myAes.Key = contentEncryptionKey; - myAes.IV = encryptionData.ContentEncryptionIV; - - byte[] src = Convert.FromBase64String(encryptedMessage.EncryptedMessageContents); - using (ICryptoTransform decryptor = myAes.CreateDecryptor()) { - return decryptor.TransformFinalBlock(src, 0, src.Length); + myAes.Key = contentEncryptionKey; + myAes.IV = encryptionData.ContentEncryptionIV; + + byte[] src = Convert.FromBase64String(encryptedMessage.EncryptedMessageContents); + using (ICryptoTransform decryptor = myAes.CreateDecryptor()) + { + return decryptor.TransformFinalBlock(src, 0, src.Length); + } } - } - default: - throw new StorageException(SR.InvalidEncryptionAlgorithm, null) { IsRetryable = false }; + default: + throw new StorageException(SR.InvalidEncryptionAlgorithm, null) { IsRetryable = false }; + } + } + else + { + return Convert.FromBase64String(encryptedMessage.EncryptedMessageContents); } } catch (JsonException ex) diff --git a/Lib/ClassLibraryCommon/Shared/Protocol/HttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/Shared/Protocol/HttpWebRequestFactory.cs index 9fe2cc21e..0055af920 100644 --- a/Lib/ClassLibraryCommon/Shared/Protocol/HttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/Shared/Protocol/HttpWebRequestFactory.cs @@ -34,7 +34,7 @@ internal static class HttpWebRequestFactory /// The request URI. /// The timeout. /// A object specifying additional parameters to add to the URI query string. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// /// A web request for performing the operation. @@ -63,17 +63,11 @@ internal static HttpWebRequest CreateWebRequest(string method, Uri uri, int? tim HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uriRequest); request.Method = method; - // Set the Content-Length of requests to 0 by default. If we do not upload - // a body, signing requires it to be 0. On Windows Phone, all requests except - // GET need this. On desktop, however, only PUT requests need it. -#if WINDOWS_DESKTOP && !WINDOWS_PHONE - if (method.Equals(WebRequestMethods.Http.Put, StringComparison.OrdinalIgnoreCase)) -#else - if (!method.Equals(WebRequestMethods.Http.Get, StringComparison.OrdinalIgnoreCase)) -#endif - { - request.ContentLength = 0; - } + // Set the Content-Length of requests to 0 by default for all put requests. + if (method.Equals(WebRequestMethods.Http.Put, StringComparison.OrdinalIgnoreCase)) + { + request.ContentLength = 0; + } request.UserAgent = Constants.HeaderConstants.UserAgent; @@ -98,7 +92,7 @@ internal static HttpWebRequest CreateWebRequest(string method, Uri uri, int? tim /// The URI to create. /// The timeout. /// The builder. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A web request for performing the operation. internal static HttpWebRequest Create(Uri uri, int? timeout, UriQueryBuilder builder, bool useVersionHeader, OperationContext operationContext) @@ -113,7 +107,7 @@ internal static HttpWebRequest Create(Uri uri, int? timeout, UriQueryBuilder bui /// The absolute URI to the resource. /// The server timeout interval. /// An optional query builder to use. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A web request to use to perform the operation. internal static HttpWebRequest GetAcl(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -140,7 +134,7 @@ internal static HttpWebRequest GetAcl(Uri uri, UriQueryBuilder builder, int? tim /// The absolute URI to the resource. /// The server timeout interval. /// An optional query builder to use. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A web request to use to perform the operation. internal static HttpWebRequest SetAcl(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -167,7 +161,7 @@ internal static HttpWebRequest SetAcl(Uri uri, UriQueryBuilder builder, int? tim /// The URI to query. /// The timeout. /// The builder. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A web request for performing the operation. internal static HttpWebRequest GetProperties(Uri uri, int? timeout, UriQueryBuilder builder, bool useVersionHeader, OperationContext operationContext) @@ -182,7 +176,7 @@ internal static HttpWebRequest GetProperties(Uri uri, int? timeout, UriQueryBuil /// The blob Uri. /// The timeout. /// The builder. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A web request for performing the operation. internal static HttpWebRequest GetMetadata(Uri uri, int? timeout, UriQueryBuilder builder, bool useVersionHeader, OperationContext operationContext) @@ -204,7 +198,7 @@ internal static HttpWebRequest GetMetadata(Uri uri, int? timeout, UriQueryBuilde /// The blob Uri. /// The timeout. /// The builder. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A web request for performing the operation. internal static HttpWebRequest SetMetadata(Uri uri, int? timeout, UriQueryBuilder builder, bool useVersionHeader, OperationContext operationContext) @@ -259,7 +253,7 @@ internal static void AddMetadata(HttpWebRequest request, string name, string val /// The URI of the resource to delete. /// The timeout. /// The builder. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A web request for performing the operation. internal static HttpWebRequest Delete(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -274,7 +268,7 @@ internal static HttpWebRequest Delete(Uri uri, UriQueryBuilder builder, int? tim /// The absolute URI to the service. /// A object specifying additional parameters to add to the URI query string. /// The server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// /// A web request to get the service properties. @@ -298,7 +292,7 @@ internal static HttpWebRequest GetServiceProperties(Uri uri, UriQueryBuilder bui /// The absolute URI to the service. /// The builder. /// The server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// /// A web request to set the service properties. @@ -322,7 +316,7 @@ internal static HttpWebRequest SetServiceProperties(Uri uri, UriQueryBuilder bui /// The absolute URI to the service. /// A object specifying additional parameters to add to the URI query string. /// The server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// /// A web request to get the service stats. diff --git a/Lib/ClassLibraryCommon/Shared/Protocol/WebRequestExtensions.cs b/Lib/ClassLibraryCommon/Shared/Protocol/WebRequestExtensions.cs index b6c2d31e9..b1d60cc9e 100644 --- a/Lib/ClassLibraryCommon/Shared/Protocol/WebRequestExtensions.cs +++ b/Lib/ClassLibraryCommon/Shared/Protocol/WebRequestExtensions.cs @@ -112,6 +112,20 @@ internal static void ApplySequenceNumberCondition(this HttpWebRequest request, A } } + /// + /// Applies the append condition to the web request. + /// + /// The request to be modified. + /// Access condition to be added to the request. + internal static void ApplyAppendCondition(this HttpWebRequest request, AccessCondition accessCondition) + { + if (accessCondition != null) + { + request.AddOptionalHeader(Constants.HeaderConstants.IfMaxSizeLessThanOrEqualHeader, accessCondition.IfMaxSizeLessThanOrEqual); + request.AddOptionalHeader(Constants.HeaderConstants.IfAppendPositionEqualHeader, accessCondition.IfAppendPositionEqual); + } + } + /// /// Applies the condition to the web request. /// diff --git a/Lib/ClassLibraryCommon/Table/CloudTable.cs b/Lib/ClassLibraryCommon/Table/CloudTable.cs index 3dadd668c..94c568824 100644 --- a/Lib/ClassLibraryCommon/Table/CloudTable.cs +++ b/Lib/ClassLibraryCommon/Table/CloudTable.cs @@ -1978,6 +1978,7 @@ private RESTCommand SetAclImpl(TablePermissions permissions, TableRequ RESTCommand retCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); retCmd.BuildRequestDelegate = TableHttpWebRequestFactory.SetAcl; retCmd.SendStream = str; + retCmd.StreamToDispose = str; retCmd.RecoveryAction = RecoveryActions.RewindStream; retCmd.SignRequest = this.ServiceClient.AuthenticationHandler.SignRequest; retCmd.ParseError = StorageExtendedErrorInformation.ReadFromStreamUsingODataLib; diff --git a/Lib/ClassLibraryCommon/Table/CloudTableClient.cs b/Lib/ClassLibraryCommon/Table/CloudTableClient.cs index 71df7038a..3c3d566cf 100644 --- a/Lib/ClassLibraryCommon/Table/CloudTableClient.cs +++ b/Lib/ClassLibraryCommon/Table/CloudTableClient.cs @@ -696,6 +696,7 @@ private RESTCommand SetServicePropertiesImpl(ServiceProperties propert } catch (InvalidOperationException invalidOpException) { + str.Dispose(); throw new ArgumentException(invalidOpException.Message, "properties"); } @@ -703,6 +704,7 @@ private RESTCommand SetServicePropertiesImpl(ServiceProperties propert RESTCommand retCmd = new RESTCommand(this.Credentials, this.StorageUri); retCmd.SendStream = str; + retCmd.StreamToDispose = str; retCmd.BuildRequestDelegate = TableHttpWebRequestFactory.SetServiceProperties; retCmd.RecoveryAction = RecoveryActions.RewindStream; retCmd.SignRequest = this.AuthenticationHandler.SignRequest; diff --git a/Lib/ClassLibraryCommon/Table/Protocol/TableHttpWebRequestFactory.cs b/Lib/ClassLibraryCommon/Table/Protocol/TableHttpWebRequestFactory.cs index 82888eeae..fc3dccb80 100644 --- a/Lib/ClassLibraryCommon/Table/Protocol/TableHttpWebRequestFactory.cs +++ b/Lib/ClassLibraryCommon/Table/Protocol/TableHttpWebRequestFactory.cs @@ -48,7 +48,7 @@ public static HttpWebRequest GetServiceProperties(Uri uri, UriQueryBuilder build /// A specifying the Table service endpoint. /// A object specifying additional parameters to add to the URI query string. /// The server timeout interval, in seconds. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. internal static HttpWebRequest GetServiceProperties(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -75,7 +75,7 @@ public static HttpWebRequest SetServiceProperties(Uri uri, UriQueryBuilder build /// A specifying the Table service endpoint. /// A object specifying additional parameters to add to the URI query string. /// The server timeout interval, in seconds. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. internal static HttpWebRequest SetServiceProperties(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -102,7 +102,7 @@ public static HttpWebRequest GetServiceStats(Uri uri, UriQueryBuilder builder, i /// A specifying the Table service endpoint. /// A object specifying additional parameters to add to the URI query string. /// The server timeout interval, in seconds. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. internal static HttpWebRequest GetServiceStats(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -141,7 +141,7 @@ public static HttpWebRequest GetAcl(Uri uri, UriQueryBuilder builder, int? timeo /// A specifying the absolute URI for the table. /// A object specifying additional parameters to add to the URI query string. /// An integer specifying the server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest GetAcl(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) @@ -168,7 +168,7 @@ public static HttpWebRequest SetAcl(Uri uri, UriQueryBuilder builder, int? timeo /// A specifying the absolute URI for the table. /// A object specifying additional parameters to add to the URI query string. /// An integer specifying the server timeout interval. - /// A flag indicating whether to set the x-ms-version HTTP header. + /// A boolean value indicating whether to set the x-ms-version HTTP header. /// An object that represents the context for the current operation. /// A object. public static HttpWebRequest SetAcl(Uri uri, UriQueryBuilder builder, int? timeout, bool useVersionHeader, OperationContext operationContext) diff --git a/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpRequestFactory.cs b/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpRequestFactory.cs index 769a8f49a..23daf652d 100644 --- a/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpRequestFactory.cs +++ b/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpRequestFactory.cs @@ -52,6 +52,7 @@ internal static Tuple BuildRequestForTableOperation(Uri HttpWebRequest msg = BuildRequestCore(uri, builder, operation.HttpMethod, timeout, useVersionHeader, ctx); TablePayloadFormat payloadFormat = options.PayloadFormat.Value; + // Set Accept and Content-Type based on the payload format. SetAcceptHeaderForHttpWebRequest(msg, payloadFormat); Logger.LogInformational(ctx, SR.PayloadFormat, payloadFormat); @@ -63,10 +64,7 @@ internal static Tuple BuildRequestForTableOperation(Uri if (operation.OperationType == TableOperationType.InsertOrMerge || operation.OperationType == TableOperationType.Merge) { - if (options.EncryptionPolicy != null) - { - throw new InvalidOperationException(SR.EncryptionNotSupportedForOperation); - } + options.AssertNoEncryptionPolicyOrStrictMode(); // post tunnelling msg.Headers.Add("X-HTTP-Method", "MERGE"); @@ -154,11 +152,7 @@ internal static Tuple BuildRequestForTableBatchOperation string httpMethod = operation.HttpMethod; if (operation.OperationType == TableOperationType.Merge || operation.OperationType == TableOperationType.InsertOrMerge) { - if (options.EncryptionPolicy != null) - { - throw new InvalidOperationException(SR.EncryptionNotSupportedForOperation); - } - + options.AssertNoEncryptionPolicyOrStrictMode(); httpMethod = "MERGE"; } @@ -222,9 +216,14 @@ private static void WriteOdataEntity(ITableEntity entity, TableOperationType ope internal static IEnumerable GetPropertiesFromDictionary(IDictionary properties, TableRequestOptions options, string partitionKey, string rowKey) { // Check if encryption policy is set and invoke EncryptEnity if it is set. - if (options.EncryptionPolicy != null) + if (options != null) { - properties = options.EncryptionPolicy.EncryptEntity(properties, partitionKey, rowKey, options.EncryptionResolver); + options.AssertPolicyIfRequired(); + + if (options.EncryptionPolicy != null) + { + properties = options.EncryptionPolicy.EncryptEntity(properties, partitionKey, rowKey, options.EncryptionResolver); + } } return properties.Select(kvp => new ODataProperty() { Name = kvp.Key, Value = kvp.Value.PropertyAsObject }); diff --git a/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpResponseParsers.cs b/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpResponseParsers.cs index d57ed210a..7935485fb 100644 --- a/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpResponseParsers.cs +++ b/Lib/ClassLibraryCommon/Table/Protocol/TableOperationHttpResponseParsers.cs @@ -469,6 +469,7 @@ private static T ReadAndResolve(ODataEntry entry, Func properties = new Dictionary(); @@ -494,19 +495,30 @@ private static T ReadAndResolve(ODataEntry entry, Func encryptedPropertyDetailsSet = null; - - if (properties.TryGetValue(Constants.EncryptionConstants.TableEncryptionPropertyDetails, out propertyDetailsProperty)) + if (properties.TryGetValue(Constants.EncryptionConstants.TableEncryptionPropertyDetails, out propertyDetailsProperty) + && properties.TryGetValue(Constants.EncryptionConstants.TableEncryptionKeyDetails, out keyProperty)) { + // Decrypt the metadata property value to get the names of encrypted properties. + EncryptionData encryptionData = null; + cek = options.EncryptionPolicy.DecryptMetadataAndReturnCEK(pk, rk, keyProperty, propertyDetailsProperty, out encryptionData); + byte[] binaryVal = propertyDetailsProperty.BinaryValue; - encryptedPropertyDetailsSet = JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(binaryVal, 0, binaryVal.Length)); - } + HashSet encryptedPropertyDetailsSet = JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(binaryVal, 0, binaryVal.Length)); - properties = options.EncryptionPolicy.DecryptEntity(properties, encryptedPropertyDetailsSet); + properties = options.EncryptionPolicy.DecryptEntity(properties, encryptedPropertyDetailsSet, pk, rk, cek, encryptionData); + } + else + { + if (options.RequireEncryption.HasValue && options.RequireEncryption.Value) + { + throw new StorageException(SR.EncryptionDataNotPresentError, null) { IsRetryable = false }; + } + } } return resolver(pk, rk, ts, properties, entry.ETag); @@ -516,6 +528,8 @@ private static T ReadAndResolveWithEdmTypeResolver(Dictionary { string pk = null; string rk = null; + byte[] cek = null; + EncryptionData encryptionData = null; DateTimeOffset ts = new DateTimeOffset(); Dictionary properties = new Dictionary(); Dictionary propertyResolverDictionary = null; @@ -537,15 +551,34 @@ private static T ReadAndResolveWithEdmTypeResolver(Dictionary #endif } - // Deserialize the metadata property value to get the names of encrypted properties so that they can be parsed correctly below. - string metadataValue = null; - if (entityAttributes.TryGetValue(Constants.EncryptionConstants.TableEncryptionPropertyDetails, out metadataValue)) + // Decrypt the metadata property value to get the names of encrypted properties so that they can be parsed correctly below. + if (options.EncryptionPolicy != null) { - EntityProperty propertyDetailsProperty = EntityProperty.CreateEntityPropertyFromObject(metadataValue, EdmType.Binary); - properties.Add(Constants.EncryptionConstants.TableEncryptionPropertyDetails, propertyDetailsProperty); + string metadataValue = null; + string keyPropertyValue = null; + + if (entityAttributes.TryGetValue(Constants.EncryptionConstants.TableEncryptionPropertyDetails, out metadataValue) + && entityAttributes.TryGetValue(Constants.EncryptionConstants.TableEncryptionKeyDetails, out keyPropertyValue)) + { + EntityProperty propertyDetailsProperty = EntityProperty.CreateEntityPropertyFromObject(metadataValue, EdmType.Binary); + EntityProperty keyProperty = EntityProperty.CreateEntityPropertyFromObject(keyPropertyValue, EdmType.String); + + entityAttributes.TryGetValue(TableConstants.PartitionKey, out pk); + entityAttributes.TryGetValue(TableConstants.RowKey, out rk); + cek = options.EncryptionPolicy.DecryptMetadataAndReturnCEK(pk, rk, keyProperty, propertyDetailsProperty, out encryptionData); - byte[] binaryVal = propertyDetailsProperty.BinaryValue; - encryptedPropertyDetailsSet = JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(binaryVal, 0, binaryVal.Length)); + properties.Add(Constants.EncryptionConstants.TableEncryptionPropertyDetails, propertyDetailsProperty); + + byte[] binaryVal = propertyDetailsProperty.BinaryValue; + encryptedPropertyDetailsSet = JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(binaryVal, 0, binaryVal.Length)); + } + else + { + if (options.RequireEncryption.HasValue && options.RequireEncryption.Value) + { + throw new StorageException(SR.EncryptionDataNotPresentError, null) { IsRetryable = false }; + } + } } foreach (KeyValuePair prop in entityAttributes) @@ -574,7 +607,15 @@ private static T ReadAndResolveWithEdmTypeResolver(Dictionary } else if (prop.Key == Constants.EncryptionConstants.TableEncryptionPropertyDetails) { - // do nothing. Already handled above. + if (!properties.ContainsKey(Constants.EncryptionConstants.TableEncryptionPropertyDetails)) + { + // If encryption policy is not set, then add the value as-is to the dictionary. + properties.Add(prop.Key, EntityProperty.CreateEntityPropertyFromObject(prop.Value, EdmType.Binary)); + } + else + { + // Do nothing. Already handled above. + } } else { @@ -623,9 +664,9 @@ private static T ReadAndResolveWithEdmTypeResolver(Dictionary } // If encryption policy is set on options, try to decrypt the entity. - if (options.EncryptionPolicy != null) + if (options.EncryptionPolicy != null && encryptionData != null) { - properties = options.EncryptionPolicy.DecryptEntity(properties, encryptedPropertyDetailsSet); + properties = options.EncryptionPolicy.DecryptEntity(properties, encryptedPropertyDetailsSet, pk, rk, cek, encryptionData); } return resolver(pk, rk, ts, properties, etag); diff --git a/Lib/ClassLibraryCommon/Table/TableEncryptionPolicy.cs b/Lib/ClassLibraryCommon/Table/TableEncryptionPolicy.cs index 0488a40a1..8bd447fca 100644 --- a/Lib/ClassLibraryCommon/Table/TableEncryptionPolicy.cs +++ b/Lib/ClassLibraryCommon/Table/TableEncryptionPolicy.cs @@ -28,7 +28,7 @@ namespace Microsoft.WindowsAzure.Storage.Table using System.Threading; /// - /// Represents a table encryption policy that is used to perform envelope encryption/decryption of Azure table entities. + /// Represents an encryption policy for performing envelope encryption/decryption of entities in Azure tables. /// public class TableEncryptionPolicy { @@ -40,7 +40,7 @@ public class TableEncryptionPolicy /// /// Gets or sets the key resolver used to select the correct key for decrypting existing table entities. /// - /// A resolver that returns an given a keyId. + /// A resolver that returns an , given a key ID. public IKeyResolver KeyResolver { get; private set; } /// @@ -48,11 +48,11 @@ public class TableEncryptionPolicy /// /// An object of type that is used to wrap/unwrap the content encryption key. /// The key resolver used to select the correct key for decrypting existing table entities. - /// If the generated policy is intended to be used for encryption, users are expected to provide a key at the minimum. - /// The absence of key will cause an exception to be thrown during encryption. - /// If the generated policy is intended to be used for decryption, users can provide a keyResolver. The client library will - - /// 1. Invoke the key resolver if specified to get the key. - /// 2. If resolver is not specified but a key is specified, match the key id on the key and use it. + /// If the generated policy is to be used for encryption, users are expected to provide a key at the minimum. + /// The absence of key will cause an exception to be thrown during encryption.
+ /// If the generated policy is intended to be used for decryption, users can provide a key resolver. The client library will:
+ /// 1. Invoke the key resolver, if specified, to get the key.
+ /// 2. If resolver is not specified but a key is specified, the client library will match the key ID against the key and use the key.
public TableEncryptionPolicy(IKey key, IKeyResolver keyResolver) { this.Key = key; @@ -116,10 +116,10 @@ internal Dictionary EncryptEntity(IDictionary(ref columnIV, 16); myAes.IV = columnIV; @@ -147,38 +147,37 @@ internal Dictionary EncryptEntity(IDictionary(ref metadataIV, 16); + myAes.IV = metadataIV; + + using (ICryptoTransform transform = myAes.CreateEncryptor()) + { + byte[] src = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(encryptionPropertyDetailsSet)); + byte[] dest = transform.TransformFinalBlock(src, 0, src.Length); + encryptedProperties.Add(Constants.EncryptionConstants.TableEncryptionPropertyDetails, new EntityProperty(dest)); + } } } + // Add the key details to entity properties. encryptedProperties.Add(Constants.EncryptionConstants.TableEncryptionKeyDetails, new EntityProperty(JsonConvert.SerializeObject(encryptionData))); - encryptedProperties.Add(Constants.EncryptionConstants.TableEncryptionPropertyDetails, new EntityProperty(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(encryptionPropertyDetailsSet)))); return encryptedProperties; } - /// - /// Return a decrypted entity. This method is used for decrypting entity properties. - /// - internal Dictionary DecryptEntity(IDictionary properties, HashSet encryptedPropertyDetailsSet) + internal byte[] DecryptMetadataAndReturnCEK(string partitionKey, string rowKey, EntityProperty encryptionKeyProperty, EntityProperty propertyDetailsProperty, out EncryptionData encryptionData) { - EntityProperty encryptionKeyProperty; - // Throw if neither the key nor the resolver are set. if (this.Key == null && this.KeyResolver == null) { throw new StorageException(SR.KeyAndResolverMissingError, null) { IsRetryable = false }; } - // If encryption policy is set but the encryption metadata is absent, throw - // an exception. - if (!properties.TryGetValue(Constants.EncryptionConstants.TableEncryptionKeyDetails, out encryptionKeyProperty) || - encryptedPropertyDetailsSet == null) - { - throw new StorageException(SR.EncryptionDataNotPresentError, null) { IsRetryable = false }; - } - try { - EncryptionData encryptionData = JsonConvert.DeserializeObject(encryptionKeyProperty.StringValue); + encryptionData = JsonConvert.DeserializeObject(encryptionKeyProperty.StringValue); CommonUtility.AssertNotNull("ContentEncryptionIV", encryptionData.ContentEncryptionIV); CommonUtility.AssertNotNull("EncryptedKey", encryptionData.WrappedContentKey.EncryptedKey); @@ -216,6 +215,53 @@ internal Dictionary DecryptEntity(IDictionary(ref metadataIV, 16); + myAes.IV = metadataIV; + myAes.Key = contentEncryptionKey; + + using (ICryptoTransform transform = myAes.CreateDecryptor()) + { + byte[] src = propertyDetailsProperty.BinaryValue; + propertyDetailsProperty.BinaryValue = transform.TransformFinalBlock(src, 0, src.Length); + } + } + } + + return contentEncryptionKey; + } + catch (JsonException ex) + { + throw new StorageException(SR.EncryptionMetadataError, ex) { IsRetryable = false }; + } + catch (StorageException) + { + throw; + } + catch (Exception ex) + { + throw new StorageException(SR.DecryptionLogicError, ex) { IsRetryable = false }; + } + } + + /// + /// Return a decrypted entity. This method is used for decrypting entity properties. + /// + internal Dictionary DecryptEntity(IDictionary properties, HashSet encryptedPropertyDetailsSet, string partitionKey, string rowKey, byte[] contentEncryptionKey, EncryptionData encryptionData) + { + try + { Dictionary decryptedProperties = new Dictionary(); switch (encryptionData.EncryptionAgent.EncryptionAlgorithm) @@ -241,7 +287,7 @@ internal Dictionary DecryptEntity(IDictionary(ref columnIV, 16); myAes.IV = columnIV; @@ -260,6 +306,7 @@ internal Dictionary DecryptEntity(IDictionary ReplaceImpl(TableOperation operation, Cl private static RESTCommand RetrieveImpl(TableOperation operation, CloudTableClient client, CloudTable table, TableRequestOptions requestOptions) { + requestOptions.AssertPolicyIfRequired(); + RESTCommand retrieveCmd = new RESTCommand(client.Credentials, operation.GenerateRequestURI(client.StorageUri, table.Name)); requestOptions.ApplyToStorageCommand(retrieveCmd); diff --git a/Lib/ClassLibraryCommon/Table/TableQuery.cs b/Lib/ClassLibraryCommon/Table/TableQuery.cs index b1344fc5e..b4f5a502c 100644 --- a/Lib/ClassLibraryCommon/Table/TableQuery.cs +++ b/Lib/ClassLibraryCommon/Table/TableQuery.cs @@ -535,8 +535,10 @@ internal TableQuerySegment EndExecuteQuerySegmentedInternal(IA private static RESTCommand> QueryImpl(TableQuery query, TableContinuationToken token, CloudTableClient client, CloudTable table, EntityResolver resolver, TableRequestOptions requestOptions) { + requestOptions.AssertPolicyIfRequired(); + // If encryption policy is set, then add the encryption metadata column to Select columns in order to be able to decrypt properties. - if (requestOptions.EncryptionPolicy != null) + if (requestOptions.EncryptionPolicy != null && query.SelectColumns != null) { query.SelectColumns.Add(Constants.EncryptionConstants.TableEncryptionKeyDetails); query.SelectColumns.Add(Constants.EncryptionConstants.TableEncryptionPropertyDetails); diff --git a/Lib/ClassLibraryCommon/Table/TableQueryNonGeneric.cs b/Lib/ClassLibraryCommon/Table/TableQueryNonGeneric.cs index 4548aeea1..15c856272 100644 --- a/Lib/ClassLibraryCommon/Table/TableQueryNonGeneric.cs +++ b/Lib/ClassLibraryCommon/Table/TableQueryNonGeneric.cs @@ -155,8 +155,10 @@ internal TableQuerySegment EndExecuteQuerySegmented(IAsyncRe private static RESTCommand> QueryImpl(TableQuery query, TableContinuationToken token, CloudTableClient client, CloudTable table, TableRequestOptions requestOptions) { + requestOptions.AssertPolicyIfRequired(); + // If encryption policy is set, then add the encryption metadata column to Select columns in order to be able to decrypt properties. - if (requestOptions.EncryptionPolicy != null) + if (requestOptions.EncryptionPolicy != null && query.SelectColumns != null) { query.SelectColumns.Add(Constants.EncryptionConstants.TableEncryptionKeyDetails); query.SelectColumns.Add(Constants.EncryptionConstants.TableEncryptionPropertyDetails); diff --git a/Lib/Common/AccessCondition.cs b/Lib/Common/AccessCondition.cs index d008489b7..78b2bdda3 100644 --- a/Lib/Common/AccessCondition.cs +++ b/Lib/Common/AccessCondition.cs @@ -89,6 +89,30 @@ public DateTimeOffset? IfNotModifiedSinceTime } } + /// + /// Gets or sets a value for a condition that specifies the maximum size allowed for an append blob when a new block is committed. The append + /// will succeed only if the size of the blob after the append operation is less than or equal to the specified size. + /// + /// The maximum size in bytes, or null if no value is set. + /// This condition only applies to append blobs. + public long? IfMaxSizeLessThanOrEqual + { + get; + set; + } + + /// + /// Gets or sets a value for a condition specifying the byte offset to check for when committing a block to an append blob. + /// The append will succeed only if the end position is equal to this number. + /// + /// An append position number, or null if no value is set. + /// This condition only applies to append blobs. + public long? IfAppendPositionEqual + { + get; + set; + } + /// /// Gets or sets a value for a condition specifying that the current sequence number must be less than or equal to the specified value. /// @@ -156,6 +180,26 @@ public static AccessCondition GenerateEmptyCondition() return new AccessCondition(); } + /// + /// Constructs an access condition such that an operation will be performed only if the resource does not exist. + /// + /// An object that represents a condition where a resource does not exist. + /// Setting this access condition modifies the request to include the HTTP If-None-Match conditional header. + public static AccessCondition GenerateIfNotExistsCondition() + { + return new AccessCondition { IfNoneMatchETag = "*" }; + } + + /// + /// Constructs an access condition such that an operation will be performed only if the resource exists. + /// + /// An object that represents a condition where a resource exists. + /// Setting this access condition modifies the request to include the HTTP If-Match conditional header. + public static AccessCondition GenerateIfExistsCondition() + { + return new AccessCondition { IfMatchETag = "*" }; + } + /// /// Constructs an access condition such that an operation will be performed only if the resource's ETag value /// matches the specified ETag value. @@ -204,6 +248,27 @@ public static AccessCondition GenerateIfNotModifiedSinceCondition(DateTimeOffset return new AccessCondition { IfNotModifiedSinceTime = modifiedTime }; } + /// + /// Constructs an access condition such that an operation will be performed only if the size of the append blob after committing the block is less + /// than or equal to the specified value. + /// + /// An integer specifying the maximum allowed size of the blob, in bytes, when committing a new block. + /// An object that represents the maximum allowed size. + public static AccessCondition GenerateIfMaxSizeLessThanOrEqualCondition(long maxSize) + { + return new AccessCondition { IfMaxSizeLessThanOrEqual = maxSize }; + } + + /// + /// Constructs an access condition such that an operation will be performed only if the end position of the append blob is equal to the specified value. + /// + /// An integer specifying the offset to compare to the current end position of the blob. + /// An object that represents the offset to compare. + public static AccessCondition GenerateIfAppendPositionEqualCondition(long appendPosition) + { + return new AccessCondition { IfAppendPositionEqual = appendPosition }; + } + /// /// Constructs an access condition such that an operation will be performed only if resource's current sequence /// number is less than or equal to the specified value. diff --git a/Lib/Common/Blob/BlobProperties.cs b/Lib/Common/Blob/BlobProperties.cs index ad8bd7ec8..874d584a6 100644 --- a/Lib/Common/Blob/BlobProperties.cs +++ b/Lib/Common/Blob/BlobProperties.cs @@ -53,6 +53,7 @@ public BlobProperties(BlobProperties other) this.ETag = other.ETag; this.LastModified = other.LastModified; this.PageBlobSequenceNumber = other.PageBlobSequenceNumber; + this.AppendBlobCommittedBlockCount = other.AppendBlobCommittedBlockCount; } /// @@ -150,5 +151,11 @@ public BlobProperties(BlobProperties other) /// /// A long containing the blob's current sequence number. public long? PageBlobSequenceNumber { get; internal set; } + + /// + /// If the blob is an append blob, gets the number of committed blocks. + /// + /// An integer containing the number of committed blocks. + public int? AppendBlobCommittedBlockCount { get; internal set; } } } diff --git a/Lib/Common/Blob/BlobRequestOptions.cs b/Lib/Common/Blob/BlobRequestOptions.cs index 656c05999..34a83048a 100644 --- a/Lib/Common/Blob/BlobRequestOptions.cs +++ b/Lib/Common/Blob/BlobRequestOptions.cs @@ -64,8 +64,11 @@ internal BlobRequestOptions(BlobRequestOptions other) if (other != null) { this.RetryPolicy = other.RetryPolicy; + this.AbsorbConditionalErrorsOnRetry = other.AbsorbConditionalErrorsOnRetry; #if !(WINDOWS_RT || ASPNET_K || PORTABLE) this.EncryptionPolicy = other.EncryptionPolicy; + this.RequireEncryption = other.RequireEncryption; + this.SkipEncryptionPolicyValidation = other.SkipEncryptionPolicyValidation; #endif this.LocationMode = other.LocationMode; this.ServerTimeout = other.ServerTimeout; @@ -84,10 +87,13 @@ internal static BlobRequestOptions ApplyDefaults(BlobRequestOptions options, Blo BlobRequestOptions modifiedOptions = new BlobRequestOptions(options); modifiedOptions.RetryPolicy = modifiedOptions.RetryPolicy ?? serviceClient.DefaultRequestOptions.RetryPolicy; + modifiedOptions.AbsorbConditionalErrorsOnRetry = modifiedOptions.AbsorbConditionalErrorsOnRetry + ?? serviceClient.DefaultRequestOptions.AbsorbConditionalErrorsOnRetry + ?? false; #if !(WINDOWS_RT || ASPNET_K || PORTABLE) modifiedOptions.EncryptionPolicy = modifiedOptions.EncryptionPolicy ?? serviceClient.DefaultRequestOptions.EncryptionPolicy; + modifiedOptions.RequireEncryption = modifiedOptions.RequireEncryption ?? serviceClient.DefaultRequestOptions.RequireEncryption; #endif - modifiedOptions.LocationMode = (modifiedOptions.LocationMode ?? serviceClient.DefaultRequestOptions.LocationMode) ?? RetryPolicies.LocationMode.PrimaryOnly; @@ -147,12 +153,23 @@ internal void ApplyToStorageCommand(RESTCommand cmd) } #if !(WINDOWS_RT || ASPNET_K || PORTABLE) - internal void AssertNoEncryptionPolicy() + internal void AssertNoEncryptionPolicyOrStrictMode() { - if (this.EncryptionPolicy != null) + // Throw if an encryption policy is set and encryption validation is on + if (this.EncryptionPolicy != null && !this.SkipEncryptionPolicyValidation) { throw new InvalidOperationException(SR.EncryptionNotSupportedForOperation); } + + this.AssertPolicyIfRequired(); + } + + internal void AssertPolicyIfRequired() + { + if (this.RequireEncryption.HasValue && this.RequireEncryption.Value && this.EncryptionPolicy == null) + { + throw new InvalidOperationException(SR.EncryptionPolicyMissingInStrictMode); + } } #endif @@ -173,8 +190,31 @@ internal void AssertNoEncryptionPolicy() /// /// An object of type . public BlobEncryptionPolicy EncryptionPolicy { get; set; } + + /// + /// Gets or sets a value to indicate whether data written and read by the client library should be encrypted. + /// + /// Use true to specify that data should be encrypted/decrypted for all transactions; otherwise, false. + public bool? RequireEncryption { get; set; } + + /// + /// Gets or sets a value to indicate whether validating the presence of the encryption policy should be skipped. + /// + /// Use true to skip validation; otherwise, false. + internal bool SkipEncryptionPolicyValidation { get; set; } #endif + /// + /// Gets or sets a value that indicates whether a conditional failure should be absorbed on a retry attempt + /// for the request. + /// + /// + /// This option is used only by the object in the UploadFrom* methods and + /// the BlobWriteStream methods. By default, it is set to false. Set this option to true only for single writer scenarios. + /// Setting this option to true in a multi-writer scenario may lead to corrupted blob data. + /// + public bool? AbsorbConditionalErrorsOnRetry { get; set; } + /// /// Gets or sets the location mode of the request. /// @@ -294,6 +334,7 @@ public bool? UseTransactionalMD5 /// Gets or sets a value to indicate that an MD5 hash will be calculated and stored when uploading a blob. /// /// Use true to calculate and store an MD5 hash when uploading a blob; otherwise, false. + /// This property is not supported for . #if WINDOWS_PHONE && WINDOWS_DESKTOP /// This property is not supported for Windows Phone. #elif PORTABLE diff --git a/Lib/Common/Blob/BlobType.cs b/Lib/Common/Blob/BlobType.cs index 1271b0643..43eba52f1 100644 --- a/Lib/Common/Blob/BlobType.cs +++ b/Lib/Common/Blob/BlobType.cs @@ -36,5 +36,10 @@ public enum BlobType /// A block blob. /// BlockBlob, + + /// + /// An append blob. + /// + AppendBlob } } diff --git a/Lib/Common/Blob/BlobWriteStreamBase.cs b/Lib/Common/Blob/BlobWriteStreamBase.cs index ded7ead5c..d0eb57232 100644 --- a/Lib/Common/Blob/BlobWriteStreamBase.cs +++ b/Lib/Common/Blob/BlobWriteStreamBase.cs @@ -37,10 +37,11 @@ internal abstract class BlobWriteStreamBase : { protected CloudBlockBlob blockBlob; protected CloudPageBlob pageBlob; + protected CloudAppendBlob appendBlob; protected long pageBlobSize; protected bool newPageBlob; protected long currentOffset; - protected long currentPageOffset; + protected long currentBlobOffset; protected int streamWriteSizeInBytes; protected MultiBufferMemoryStream internalBuffer; protected List blockList; @@ -67,8 +68,8 @@ private BlobWriteStreamBase(CloudBlobClient serviceClient, AccessCondition acces : base() { this.internalBuffer = new MultiBufferMemoryStream(serviceClient.BufferManager); - this.currentOffset = 0; this.accessCondition = accessCondition; + this.currentOffset = 0; this.options = options; this.operationContext = operationContext; this.noPendingWritesEvent = new CounterEvent(); @@ -91,6 +92,7 @@ protected BlobWriteStreamBase(CloudBlockBlob blockBlob, AccessCondition accessCo : this(blockBlob.ServiceClient, accessCondition, options, operationContext) { this.blockBlob = blockBlob; + this.Blob = this.blockBlob; this.blockList = new List(); this.blockIdPrefix = Guid.NewGuid().ToString("N") + "-"; this.streamWriteSizeInBytes = blockBlob.StreamWriteSizeInBytes; @@ -108,28 +110,38 @@ protected BlobWriteStreamBase(CloudBlockBlob blockBlob, AccessCondition accessCo protected BlobWriteStreamBase(CloudPageBlob pageBlob, long pageBlobSize, bool createNew, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) : this(pageBlob.ServiceClient, accessCondition, options, operationContext) { - this.currentPageOffset = 0; + this.currentBlobOffset = 0; this.pageBlob = pageBlob; + this.Blob = this.pageBlob; this.pageBlobSize = pageBlobSize; this.streamWriteSizeInBytes = pageBlob.StreamWriteSizeInBytes; this.newPageBlob = createNew; } - protected ICloudBlob Blob + /// + /// Initializes a new instance of the BlobWriteStreamBase class for an append blob. + /// + /// Blob reference to write to. + /// An object that represents the condition that must be met in order for the request to proceed. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + protected BlobWriteStreamBase(CloudAppendBlob appendBlob, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + : this(appendBlob.ServiceClient, accessCondition, options, operationContext) { - get - { - if (this.blockBlob != null) - { - return this.blockBlob; - } - else - { - return this.pageBlob; - } - } + this.accessCondition = this.accessCondition ?? new AccessCondition(); + + // If we are creating a new blob, create call would have appropriately set the length to 0. For an existing blob, if user specified a condition with append offset, + // we will use it. Otherwise, we will use the length returned by the FetchAttributes call. + this.currentBlobOffset = this.accessCondition.IfAppendPositionEqual.HasValue ? this.accessCondition.IfAppendPositionEqual.Value : appendBlob.Properties.Length; + this.operationContext = this.operationContext ?? new OperationContext(); + this.appendBlob = appendBlob; + this.Blob = this.appendBlob; + this.parallelOperationSemaphore = new AsyncSemaphore(1); + this.streamWriteSizeInBytes = appendBlob.StreamWriteSizeInBytes; } + protected CloudBlob Blob { get; private set; } + /// /// Gets a value indicating whether the current stream supports reading. /// diff --git a/Lib/Common/Blob/CloudAppendBlob.Common.cs b/Lib/Common/Blob/CloudAppendBlob.Common.cs new file mode 100644 index 000000000..8820b72cf --- /dev/null +++ b/Lib/Common/Blob/CloudAppendBlob.Common.cs @@ -0,0 +1,131 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.Blob +{ + using Microsoft.WindowsAzure.Storage.Auth; + using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; + + /// + /// Represents an append blob, a type of blob where blocks of data are always committed to the end of the blob. + /// + public sealed partial class CloudAppendBlob : CloudBlob, ICloudBlob + { + /// + /// Default is 4 MB. + /// + private int streamWriteSizeInBytes = Constants.DefaultWriteBlockSizeBytes; + + /// + /// Initializes a new instance of the class using an absolute URI to the blob. + /// + /// A specifying the absolute URI to the blob. + public CloudAppendBlob(Uri blobAbsoluteUri) + : this(blobAbsoluteUri, null /* credentials */) + { + } + + /// + /// Initializes a new instance of the class using an absolute URI to the blob. + /// + /// A specifying the absolute URI to the blob. + /// A object. + public CloudAppendBlob(Uri blobAbsoluteUri, StorageCredentials credentials) + : this(blobAbsoluteUri, null /* snapshotTime */, credentials) + { + } + + /// + /// Initializes a new instance of the class using an absolute URI to the blob. + /// + /// A specifying the absolute URI to the blob. + /// A specifying the snapshot timestamp, if the blob is a snapshot. + /// A object. + public CloudAppendBlob(Uri blobAbsoluteUri, DateTimeOffset? snapshotTime, StorageCredentials credentials) + : this(new StorageUri(blobAbsoluteUri), snapshotTime, credentials) + { + } + + /// + /// Initializes a new instance of the class using an absolute URI to the blob. + /// + /// A containing the absolute URI to the blob at both the primary and secondary locations. + /// A specifying the snapshot timestamp, if the blob is a snapshot. + /// A object. +#if WINDOWS_RT || ASPNET_K + /// A object. + public static new CloudAppendBlob Create(StorageUri blobAbsoluteUri, DateTimeOffset? snapshotTime, StorageCredentials credentials) + { + return new CloudAppendBlob(blobAbsoluteUri, snapshotTime, credentials); + } + + internal CloudAppendBlob(StorageUri blobAbsoluteUri, DateTimeOffset? snapshotTime, StorageCredentials credentials) + : base(blobAbsoluteUri, snapshotTime, credentials) +#else + public CloudAppendBlob(StorageUri blobAbsoluteUri, DateTimeOffset? snapshotTime, StorageCredentials credentials) + : base(blobAbsoluteUri, snapshotTime, credentials) +#endif + { + this.Properties.BlobType = BlobType.AppendBlob; + } + + /// + /// Initializes a new instance of the class using the specified blob name and + /// the parent container reference. + /// If snapshotTime is not null, the blob instance represents a Snapshot. + /// + /// Name of the blob. + /// Snapshot time in case the blob is a snapshot. + /// The reference to the parent container. + internal CloudAppendBlob(string blobName, DateTimeOffset? snapshotTime, CloudBlobContainer container) + : base(blobName, snapshotTime, container) + { + this.Properties.BlobType = BlobType.AppendBlob; + } + + /// + /// Initializes a new instance of the class. + /// + /// The attributes. + /// The service client. + internal CloudAppendBlob(BlobAttributes attributes, CloudBlobClient serviceClient) + : base(attributes, serviceClient) + { + this.Properties.BlobType = BlobType.AppendBlob; + } + + /// + /// Gets or sets the number of bytes to buffer when writing to an append blob stream. + /// + /// The size of a block, in bytes, ranging from between 16 KB and 4 MB inclusive. + public int StreamWriteSizeInBytes + { + get + { + return this.streamWriteSizeInBytes; + } + + set + { + CommonUtility.AssertInBounds("StreamWriteSizeInBytes", value, 16 * Constants.KB, Constants.MaxBlockSize); + this.streamWriteSizeInBytes = value; + } + } + } +} diff --git a/Lib/Common/Blob/CloudBlob.Common.cs b/Lib/Common/Blob/CloudBlob.Common.cs index 0e63fdc08..5bd8877ab 100644 --- a/Lib/Common/Blob/CloudBlob.Common.cs +++ b/Lib/Common/Blob/CloudBlob.Common.cs @@ -72,7 +72,7 @@ public CloudBlob(Uri blobAbsoluteUri, DateTimeOffset? snapshotTime, StorageCrede /// A containing the absolute URI to the blob at both the primary and secondary locations. /// A specifying the snapshot timestamp, if the blob is a snapshot. /// A object. -#if WINDOWS_RT +#if WINDOWS_RT || ASPNET_K /// A object. public static CloudBlob Create(StorageUri blobAbsoluteUri, DateTimeOffset? snapshotTime, StorageCredentials credentials) { @@ -259,7 +259,7 @@ public Uri SnapshotQualifiedUri if (this.SnapshotTime.HasValue) { UriQueryBuilder builder = new UriQueryBuilder(); - builder.Add("snapshot", BlobRequest.ConvertDateTimeToSnapshotString(this.SnapshotTime.Value)); + builder.Add("snapshot", Request.ConvertDateTimeToSnapshotString(this.SnapshotTime.Value)); return builder.AddToUri(this.Uri); } else @@ -281,7 +281,7 @@ public StorageUri SnapshotQualifiedStorageUri if (this.SnapshotTime.HasValue) { UriQueryBuilder builder = new UriQueryBuilder(); - builder.Add("snapshot", BlobRequest.ConvertDateTimeToSnapshotString(this.SnapshotTime.Value)); + builder.Add("snapshot", Request.ConvertDateTimeToSnapshotString(this.SnapshotTime.Value)); return builder.AddToUri(this.StorageUri); } else @@ -375,7 +375,7 @@ internal set /// The query string returned includes the leading question mark. public string GetSharedAccessSignature(SharedAccessBlobPolicy policy) { - return this.GetSharedAccessSignature(policy, null /* headers */, null /* groupPolicyIdentifier */, null /* sasVersion */); + return this.GetSharedAccessSignature(policy, null /* headers */, null /* groupPolicyIdentifier */); } /// @@ -390,7 +390,7 @@ public string GetSharedAccessSignature(SharedAccessBlobPolicy policy) #endif public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, string groupPolicyIdentifier) { - return this.GetSharedAccessSignature(policy, null /* headers */, groupPolicyIdentifier, null /* sasVersion */); + return this.GetSharedAccessSignature(policy, null /* headers */, groupPolicyIdentifier); } /// @@ -401,7 +401,7 @@ public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, string gro /// A shared access signature, as a URI query string. public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAccessBlobHeaders headers) { - return this.GetSharedAccessSignature(policy, headers, null /* groupPolicyIdentifier */, null /* sasVersion */); + return this.GetSharedAccessSignature(policy, headers, null /* groupPolicyIdentifier */); } /// @@ -413,7 +413,20 @@ public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAcce /// A shared access signature, as a URI query string. public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAccessBlobHeaders headers, string groupPolicyIdentifier) { - return this.GetSharedAccessSignature(policy, headers, groupPolicyIdentifier, null /* sasVersion */); + if (!this.ServiceClient.Credentials.IsSharedKey) + { + string errorMessage = string.Format(CultureInfo.InvariantCulture, SR.CannotCreateSASWithoutAccountKey); + throw new InvalidOperationException(errorMessage); + } + + string resourceName = this.GetCanonicalName(true /* ignoreSnapshotTime */, Constants.HeaderConstants.TargetStorageVersion); + StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; + string signature = SharedAccessSignatureHelper.GetHash(policy, headers, groupPolicyIdentifier, resourceName, Constants.HeaderConstants.TargetStorageVersion, accountKey.KeyValue); + + // Future resource type changes from "c" => "container" + UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature(policy, headers, groupPolicyIdentifier, "b", signature, accountKey.KeyName, Constants.HeaderConstants.TargetStorageVersion); + + return builder.ToString(); } /// @@ -424,6 +437,7 @@ public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAcce /// A string identifying a stored access policy. /// A string indicating the desired SAS version to use, in storage service version format. Value must be 2012-02-12 or 2013-08-15. /// A shared access signature, as a URI query string. + [Obsolete("This overload has been deprecated because the SAS tokens generated using the current version work fine with old libraries. Please use the other overloads.")] public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAccessBlobHeaders headers, string groupPolicyIdentifier, string sasVersion) { if (!this.ServiceClient.Credentials.IsSharedKey) @@ -433,7 +447,7 @@ public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAcce } string validatedSASVersion = SharedAccessSignatureHelper.ValidateSASVersionString(sasVersion); - string resourceName = this.GetCanonicalName(true /* ignoreSnapshotTime */); + string resourceName = this.GetCanonicalName(true /* ignoreSnapshotTime */, validatedSASVersion); StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; string signature = SharedAccessSignatureHelper.GetHash(policy, headers, groupPolicyIdentifier, resourceName, validatedSASVersion, accountKey.KeyValue); @@ -451,20 +465,27 @@ public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, SharedAcce /// This is used by both Shared Access and Copy blob operations. /// /// Indicates if the snapshot time is ignored. + /// A string indicating the desired SAS version to use, in storage service version format. Value must be 2012-02-12 or 2013-08-15. /// The canonical name of the blob. - private string GetCanonicalName(bool ignoreSnapshotTime) + private string GetCanonicalName(bool ignoreSnapshotTime, string sasVersion) { string accountName = this.ServiceClient.Credentials.AccountName; string containerName = this.Container.Name; - + // Replace \ with / for uri compatibility when running under .net 4.5. string blobName = this.Name.Replace('\\', '/'); - - string canonicalName = string.Format(CultureInfo.InvariantCulture, "/{0}/{1}/{2}", accountName, containerName, blobName); - - if (!ignoreSnapshotTime && this.SnapshotTime != null) + string canonicalNameFormat = "/{0}/{1}/{2}/{3}"; + if (sasVersion == Constants.VersionConstants.February2012 || sasVersion == Constants.VersionConstants.August2013) { - canonicalName += "?snapshot=" + BlobRequest.ConvertDateTimeToSnapshotString(this.SnapshotTime.Value); + // Do not prepend service name for older versions + canonicalNameFormat = "/{1}/{2}/{3}"; + } + + string canonicalName = string.Format(CultureInfo.InvariantCulture, canonicalNameFormat, SR.Blob, accountName, containerName, blobName); + + if (!ignoreSnapshotTime && this.SnapshotTime != null) + { + canonicalName += "?snapshot=" + Request.ConvertDateTimeToSnapshotString(this.SnapshotTime.Value); } return canonicalName; @@ -481,7 +502,7 @@ private void ParseQueryAndVerify(StorageUri address, StorageCredentials credenti DateTimeOffset? parsedSnapshot; this.attributes.StorageUri = NavigationHelper.ParseBlobQueryAndVerify(address, out parsedCredentials, out parsedSnapshot); - if ((parsedCredentials != null) && (credentials != null) && !parsedCredentials.Equals(credentials)) + if (parsedCredentials != null && credentials != null) { string error = string.Format(CultureInfo.CurrentCulture, SR.MultipleCredentialsProvided); throw new ArgumentException(error); diff --git a/Lib/Common/Blob/CloudBlobContainer.Common.cs b/Lib/Common/Blob/CloudBlobContainer.Common.cs index fe139a2ab..a7310012f 100644 --- a/Lib/Common/Blob/CloudBlobContainer.Common.cs +++ b/Lib/Common/Blob/CloudBlobContainer.Common.cs @@ -21,6 +21,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Auth; using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Collections.Generic; using System.Globalization; @@ -153,7 +154,7 @@ private void ParseQueryAndVerify(StorageUri address, StorageCredentials credenti DateTimeOffset? parsedSnapshot; this.StorageUri = NavigationHelper.ParseBlobQueryAndVerify(address, out parsedCredentials, out parsedSnapshot); - if ((parsedCredentials != null) && (credentials != null) && !parsedCredentials.Equals(credentials)) + if (parsedCredentials != null && credentials != null) { string error = string.Format(CultureInfo.CurrentCulture, SR.MultipleCredentialsProvided); throw new ArgumentException(error); @@ -166,13 +167,21 @@ private void ParseQueryAndVerify(StorageUri address, StorageCredentials credenti /// /// Returns the canonical name for shared access. /// + /// A string indicating the desired SAS version to get canonical name for, in storage service version format. /// The canonical name. - private string GetSharedAccessCanonicalName() + private string GetSharedAccessCanonicalName(string sasVersion) { string accountName = this.ServiceClient.Credentials.AccountName; string containerName = this.Name; - return string.Format(CultureInfo.InvariantCulture, "/{0}/{1}", accountName, containerName); + string canonicalNameFormat = "/{0}/{1}/{2}"; + if (sasVersion == Constants.VersionConstants.February2012 || sasVersion == Constants.VersionConstants.August2013) + { + // Do not prepend service name for older versions + canonicalNameFormat = "/{1}/{2}"; + } + + return string.Format(CultureInfo.InvariantCulture, canonicalNameFormat, SR.Blob, accountName, containerName); } #if !PORTABLE @@ -216,7 +225,7 @@ public string GetSharedAccessSignature(SharedAccessBlobPolicy policy, string gro } string validatedSASVersion = SharedAccessSignatureHelper.ValidateSASVersionString(sasVersion); - string resourceName = this.GetSharedAccessCanonicalName(); + string resourceName = this.GetSharedAccessCanonicalName(validatedSASVersion); StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; string signature = SharedAccessSignatureHelper.GetHash(policy, null /* headers */, groupPolicyIdentifier, resourceName, validatedSASVersion, accountKey.KeyValue); string accountKeyName = accountKey.KeyName; @@ -272,11 +281,33 @@ public CloudBlockBlob GetBlockBlobReference(string blobName, DateTimeOffset? sna return new CloudBlockBlob(blobName, snapshotTime, this); } + /// + /// Gets a reference to an append blob in this container. + /// + /// A string containing the name of the append blob. + /// A object. + public CloudAppendBlob GetAppendBlobReference(string blobName) + { + return this.GetAppendBlobReference(blobName, null /* snapshotTime */); + } + + /// + /// Gets a reference to an append blob in this container. + /// + /// A string containing the name of the append blob. + /// A specifying the snapshot timestamp, if the blob is a snapshot. + /// A object. + public CloudAppendBlob GetAppendBlobReference(string blobName, DateTimeOffset? snapshotTime) + { + CommonUtility.AssertNotNullOrEmpty("blobName", blobName); + return new CloudAppendBlob(blobName, snapshotTime, this); + } + /// /// Gets a reference to a blob in this container. /// /// A string containing the name of the blob. - /// A object. + /// A object. public CloudBlob GetBlobReference(string blobName) { return this.GetBlobReference(blobName, null /* snapshotTime */); @@ -287,7 +318,7 @@ public CloudBlob GetBlobReference(string blobName) /// /// A string containing the name of the blob. /// A specifying the snapshot timestamp, if the blob is a snapshot. - /// A object. + /// A object. public CloudBlob GetBlobReference(string blobName, DateTimeOffset? snapshotTime) { CommonUtility.AssertNotNullOrEmpty("blobName", blobName); diff --git a/Lib/Common/Blob/CloudBlobDirectory.Common.cs b/Lib/Common/Blob/CloudBlobDirectory.Common.cs index b55fa0ced..3216adcac 100644 --- a/Lib/Common/Blob/CloudBlobDirectory.Common.cs +++ b/Lib/Common/Blob/CloudBlobDirectory.Common.cs @@ -154,11 +154,35 @@ public CloudBlockBlob GetBlockBlobReference(string blobName, DateTimeOffset? sna return new CloudBlockBlob(blobUri, snapshotTime, this.ServiceClient.Credentials); } + /// + /// Gets a reference to an append blob in this virtual directory. + /// + /// A string containing the name of the blob. + /// A object. + public CloudAppendBlob GetAppendBlobReference(string blobName) + { + return this.GetAppendBlobReference(blobName, null /* snapshotTime */); + } + + /// + /// Gets a reference to an append blob in this virtual directory. + /// + /// A string containing the name of the blob. + /// A specifying the snapshot timestamp, if the blob is a snapshot. + /// A object. + public CloudAppendBlob GetAppendBlobReference(string blobName, DateTimeOffset? snapshotTime) + { + CommonUtility.AssertNotNullOrEmpty("blobName", blobName); + + StorageUri blobUri = NavigationHelper.AppendPathToUri(this.StorageUri, blobName, this.ServiceClient.DefaultDelimiter); + return new CloudAppendBlob(blobUri, snapshotTime, this.ServiceClient.Credentials); + } + /// /// Gets a reference to a blob in this virtual directory. /// /// A string containing the name of the blob. - /// A object. + /// A object. public CloudBlob GetBlobReference(string blobName) { return this.GetBlobReference(blobName, null /* snapshotTime */); @@ -169,7 +193,7 @@ public CloudBlob GetBlobReference(string blobName) /// /// A string containing the name of the blob. /// A specifying the snapshot timestamp, if the blob is a snapshot. - /// A object. + /// A object. public CloudBlob GetBlobReference(string blobName, DateTimeOffset? snapshotTime) { CommonUtility.AssertNotNullOrEmpty("blobName", blobName); diff --git a/Lib/Common/Blob/Protocol/BlobErrorCodeStrings.cs b/Lib/Common/Blob/Protocol/BlobErrorCodeStrings.cs index 4acf34bcf..69628c59f 100644 --- a/Lib/Common/Blob/Protocol/BlobErrorCodeStrings.cs +++ b/Lib/Common/Blob/Protocol/BlobErrorCodeStrings.cs @@ -30,6 +30,16 @@ namespace Microsoft.WindowsAzure.Storage.Blob.Protocol #endif static class BlobErrorCodeStrings { + /// + /// Error code that may be returned when the specified append offset is invalid. + /// + public static readonly string InvalidAppendCondition = "AppendPositionConditionNotMet"; + + /// + /// Error code that may be returned when the specified maximum blob size is invalid. + /// + public static readonly string InvalidMaxBlobSizeCondition = "MaxBlobSizeConditionNotMet"; + /// /// Error code that may be returned when the specified block or blob is invalid. /// diff --git a/Lib/Common/Blob/Protocol/BlobRequest.cs b/Lib/Common/Blob/Protocol/BlobRequest.cs index 1a13bdb6c..93432ed27 100644 --- a/Lib/Common/Blob/Protocol/BlobRequest.cs +++ b/Lib/Common/Blob/Protocol/BlobRequest.cs @@ -37,16 +37,6 @@ namespace Microsoft.WindowsAzure.Storage.Blob.Protocol #endif static class BlobRequest { - /// - /// Converts the date time to snapshot string. - /// - /// The date time. - /// The converted string. - internal static string ConvertDateTimeToSnapshotString(DateTimeOffset dateTime) - { - return dateTime.UtcDateTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffff'Z'", CultureInfo.InvariantCulture); - } - /// /// Writes a collection of shared access policies to the specified stream in XML format. /// diff --git a/Lib/Common/Blob/Protocol/ListBlobsResponse.cs b/Lib/Common/Blob/Protocol/ListBlobsResponse.cs index 06cf680af..7c1623e44 100644 --- a/Lib/Common/Blob/Protocol/ListBlobsResponse.cs +++ b/Lib/Common/Blob/Protocol/ListBlobsResponse.cs @@ -287,6 +287,10 @@ private IListBlobEntry ParseBlobEntry(Uri baseUri) case Constants.PageBlobValue: blob.Properties.BlobType = BlobType.PageBlob; break; + + case Constants.AppendBlobValue: + blob.Properties.BlobType = BlobType.AppendBlob; + break; } break; @@ -355,7 +359,7 @@ private IListBlobEntry ParseBlobEntry(Uri baseUri) if (blob.SnapshotTime.HasValue) { UriQueryBuilder builder = new UriQueryBuilder(); - builder.Add("snapshot", BlobRequest.ConvertDateTimeToSnapshotString(blob.SnapshotTime.Value)); + builder.Add("snapshot", Request.ConvertDateTimeToSnapshotString(blob.SnapshotTime.Value)); uri = builder.AddToUri(uri); } diff --git a/Lib/Common/Blob/SharedAccessBlobPolicies.cs b/Lib/Common/Blob/SharedAccessBlobPolicies.cs index 246f235e3..c24e5e68d 100644 --- a/Lib/Common/Blob/SharedAccessBlobPolicies.cs +++ b/Lib/Common/Blob/SharedAccessBlobPolicies.cs @@ -160,7 +160,7 @@ public bool Contains(KeyValuePair item) /// Copies each key in the key/ value pair to a compatible one-dimensional array, starting at the specified index /// of the target array. /// - /// The one-dimensional array of objects that is the destination of the elements copied from the shared access policies collection. + /// A one-dimensional array of objects that serves as the destination for the elements copied from the shared access policies collection. /// The zero-based index in at which copying begins. public void CopyTo(KeyValuePair[] array, int arrayIndex) { diff --git a/Lib/Common/CloudStorageAccount.cs b/Lib/Common/CloudStorageAccount.cs index 3fc794aac..66d5b95de 100644 --- a/Lib/Common/CloudStorageAccount.cs +++ b/Lib/Common/CloudStorageAccount.cs @@ -543,11 +543,6 @@ public CloudBlobClient CreateCloudBlobClient() throw new InvalidOperationException(SR.BlobEndPointNotConfigured); } - if (this.Credentials == null) - { - throw new InvalidOperationException(SR.MissingCredentials); - } - return new CloudBlobClient(this.BlobStorageUri, this.Credentials); } diff --git a/Lib/Common/Core/Auth/SharedAccessSignatureHelper.cs b/Lib/Common/Core/Auth/SharedAccessSignatureHelper.cs index 8804fbfec..12b2752f3 100644 --- a/Lib/Common/Core/Auth/SharedAccessSignatureHelper.cs +++ b/Lib/Common/Core/Auth/SharedAccessSignatureHelper.cs @@ -30,6 +30,7 @@ namespace Microsoft.WindowsAzure.Storage.Core.Auth using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; + using System.Text; /// /// Contains helper methods for implementing shared access signatures. @@ -91,6 +92,60 @@ internal static UriQueryBuilder GetSignature( return builder; } + /// + /// Get the complete query builder for creating the Shared Access Signature query. + /// + /// The shared access policy to hash. + /// The optional header values to set for a file returned with this SAS. + /// An optional identifier for the policy. + /// Either "f" for files or "s" for shares. + /// The signature to use. + /// The name of the key used to create the signature, or null if the key is implicit. + /// A string indicating the desired SAS version to use, in storage service version format. + /// The finished query builder. + internal static UriQueryBuilder GetSignature( + SharedAccessFilePolicy policy, + SharedAccessFileHeaders headers, + string accessPolicyIdentifier, + string resourceType, + string signature, + string accountKeyName, + string sasVersion) + { + CommonUtility.AssertNotNullOrEmpty("resourceType", resourceType); + + UriQueryBuilder builder = new UriQueryBuilder(); + + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedVersion, sasVersion); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedResource, resourceType); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedIdentifier, accessPolicyIdentifier); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedKey, accountKeyName); + AddEscapedIfNotNull(builder, Constants.QueryConstants.Signature, signature); + + if (policy != null) + { + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedStart, GetDateTimeOrNull(policy.SharedAccessStartTime)); + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedExpiry, GetDateTimeOrNull(policy.SharedAccessExpiryTime)); + + string permissions = SharedAccessFilePolicy.PermissionsToString(policy.Permissions); + if (!string.IsNullOrEmpty(permissions)) + { + AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedPermissions, permissions); + } + } + + if (headers != null) + { + AddEscapedIfNotNull(builder, Constants.QueryConstants.CacheControl, headers.CacheControl); + AddEscapedIfNotNull(builder, Constants.QueryConstants.ContentType, headers.ContentType); + AddEscapedIfNotNull(builder, Constants.QueryConstants.ContentEncoding, headers.ContentEncoding); + AddEscapedIfNotNull(builder, Constants.QueryConstants.ContentLanguage, headers.ContentLanguage); + AddEscapedIfNotNull(builder, Constants.QueryConstants.ContentDisposition, headers.ContentDisposition); + } + + return builder; + } + /// /// Get the complete query builder for creating the Shared Access Signature query. /// @@ -227,122 +282,24 @@ internal static void AddEscapedIfNotNull(UriQueryBuilder builder, string name, s /// Parses the query. /// /// The query parameters. - /// A boolean that represents whether SignedResource is part of Sas or not. True for blobs, False for Queues and Tables. - internal static StorageCredentials ParseQuery(IDictionary queryParameters, bool mandatorySignedResource) + internal static StorageCredentials ParseQuery(IDictionary queryParameters) { - string signature = null; - string signedStart = null; - string signedExpiry = null; - string signedResource = null; - string sigendPermissions = null; - string signedIdentifier = null; - string signedVersion = null; - string signedKey = null; - string cacheControl = null; - string contentType = null; - string contentEncoding = null; - string contentLanguage = null; - string contentDisposition = null; - string tableName = null; - string startPk = null; - string startRk = null; - string endPk = null; - string endRk = null; - bool sasParameterFound = false; + List removeList = new List(); foreach (KeyValuePair parameter in queryParameters) { switch (parameter.Key.ToLower()) { - case Constants.QueryConstants.SignedStart: - signedStart = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.SignedExpiry: - signedExpiry = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.SignedPermissions: - sigendPermissions = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.SignedResource: - signedResource = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.SignedIdentifier: - signedIdentifier = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.SignedKey: - signedKey = parameter.Value; - sasParameterFound = true; - break; - case Constants.QueryConstants.Signature: - signature = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.SignedVersion: - signedVersion = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.CacheControl: - cacheControl = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.ContentType: - contentType = parameter.Value; sasParameterFound = true; break; - case Constants.QueryConstants.ContentEncoding: - contentEncoding = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.ContentLanguage: - contentLanguage = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.ContentDisposition: - contentDisposition = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.SasTableName: - tableName = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.StartPartitionKey: - startPk = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.StartRowKey: - startRk = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.EndPartitionKey: - endPk = parameter.Value; - sasParameterFound = true; - break; - - case Constants.QueryConstants.EndRowKey: - endRk = parameter.Value; - sasParameterFound = true; + case Constants.QueryConstants.ResourceType: + case Constants.QueryConstants.Component: + case Constants.QueryConstants.Snapshot: + case Constants.QueryConstants.ApiVersion: + removeList.Add(parameter.Key); break; default: @@ -350,38 +307,19 @@ internal static StorageCredentials ParseQuery(IDictionary queryP } } - if (sasParameterFound) + foreach (string removeParam in removeList) { - if (signature == null || (mandatorySignedResource && signedResource == null)) - { - string errorMessage = string.Format(CultureInfo.CurrentCulture, SR.MissingMandatoryParametersForSAS); - throw new ArgumentException(errorMessage); - } + queryParameters.Remove(removeParam); + } + if (sasParameterFound) + { UriQueryBuilder builder = new UriQueryBuilder(); - AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedStart, signedStart); - AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedExpiry, signedExpiry); - AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedPermissions, sigendPermissions); - if (signedResource != null) + foreach (KeyValuePair parameter in queryParameters) { - builder.Add(Constants.QueryConstants.SignedResource, signedResource); + AddEscapedIfNotNull(builder, parameter.Key.ToLower(), parameter.Value); } - AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedIdentifier, signedIdentifier); - AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedVersion, signedVersion); - AddEscapedIfNotNull(builder, Constants.QueryConstants.SignedKey, signedKey); - AddEscapedIfNotNull(builder, Constants.QueryConstants.Signature, signature); - AddEscapedIfNotNull(builder, Constants.QueryConstants.CacheControl, cacheControl); - AddEscapedIfNotNull(builder, Constants.QueryConstants.ContentType, contentType); - AddEscapedIfNotNull(builder, Constants.QueryConstants.ContentEncoding, contentEncoding); - AddEscapedIfNotNull(builder, Constants.QueryConstants.ContentLanguage, contentLanguage); - AddEscapedIfNotNull(builder, Constants.QueryConstants.ContentDisposition, contentDisposition); - AddEscapedIfNotNull(builder, Constants.QueryConstants.SasTableName, tableName); - AddEscapedIfNotNull(builder, Constants.QueryConstants.StartPartitionKey, startPk); - AddEscapedIfNotNull(builder, Constants.QueryConstants.StartRowKey, startRk); - AddEscapedIfNotNull(builder, Constants.QueryConstants.EndPartitionKey, endPk); - AddEscapedIfNotNull(builder, Constants.QueryConstants.EndRowKey, endRk); - return new StorageCredentials(builder.ToString()); } @@ -609,6 +547,87 @@ internal static string GetHash( return CryptoUtility.ComputeHmac256(keyValue, stringToSign); } + + /// + /// Get the signature hash embedded inside the Shared Access Signature. + /// + /// The shared access policy to hash. + /// The optional header values to set for a file returned with this SAS. + /// An optional identifier for the policy. + /// The canonical resource string, unescaped. + /// A string indicating the desired SAS version to use, in storage service version format. + /// The key value retrieved as an atomic operation used for signing. + /// The signed hash. + internal static string GetHash( + SharedAccessFilePolicy policy, + SharedAccessFileHeaders headers, + string accessPolicyIdentifier, + string resourceName, + string sasVersion, + byte[] keyValue) + { + CommonUtility.AssertNotNullOrEmpty("resourceName", resourceName); + CommonUtility.AssertNotNull("keyValue", keyValue); + CommonUtility.AssertNotNullOrEmpty("sasVersion", sasVersion); + + string permissions = null; + DateTimeOffset? startTime = null; + DateTimeOffset? expiryTime = null; + if (policy != null) + { + permissions = SharedAccessFilePolicy.PermissionsToString(policy.Permissions); + startTime = policy.SharedAccessStartTime; + expiryTime = policy.SharedAccessExpiryTime; + } + + //// StringToSign = signedpermissions + "\n" + + //// signedstart + "\n" + + //// signedexpiry + "\n" + + //// canonicalizedresource + "\n" + + //// signedidentifier + "\n" + + //// signedversion + "\n" + + //// cachecontrol + "\n" + + //// contentdisposition + "\n" + + //// contentencoding + "\n" + + //// contentlanguage + "\n" + + //// contenttype + //// + //// HMAC-SHA256(UTF8.Encode(StringToSign)) + //// + + string cacheControl = null; + string contentDisposition = null; + string contentEncoding = null; + string contentLanguage = null; + string contentType = null; + if (headers != null) + { + cacheControl = headers.CacheControl; + contentDisposition = headers.ContentDisposition; + contentEncoding = headers.ContentEncoding; + contentLanguage = headers.ContentLanguage; + contentType = headers.ContentType; + } + + string stringToSign = string.Format( + CultureInfo.InvariantCulture, + "{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}\n{7}\n{8}\n{9}\n{10}", + permissions, + GetDateTimeOrEmpty(startTime), + GetDateTimeOrEmpty(expiryTime), + resourceName, + accessPolicyIdentifier, + sasVersion, + cacheControl, + contentDisposition, + contentEncoding, + contentLanguage, + contentType); + + Logger.LogVerbose(null /* operationContext */, SR.TraceStringToSign, stringToSign); + + return CryptoUtility.ComputeHmac256(keyValue, stringToSign); + } #endif /// diff --git a/Lib/Common/Core/Executor/RESTCommand.cs b/Lib/Common/Core/Executor/RESTCommand.cs index 3240bbadc..195486d79 100644 --- a/Lib/Common/Core/Executor/RESTCommand.cs +++ b/Lib/Common/Core/Executor/RESTCommand.cs @@ -124,12 +124,6 @@ public Stream SendStream set { - MultiBufferMemoryStream tempStream = value as MultiBufferMemoryStream; - if (tempStream != null) - { - this.StreamToDispose = tempStream; - } - this.sendStream = value; } } diff --git a/Lib/Common/Core/MultiBufferMemoryStream.cs b/Lib/Common/Core/MultiBufferMemoryStream.cs index 301b525a5..654e91a7a 100644 --- a/Lib/Common/Core/MultiBufferMemoryStream.cs +++ b/Lib/Common/Core/MultiBufferMemoryStream.cs @@ -102,7 +102,7 @@ public override bool CanRead { get { - return true; + return !this.disposed; } } @@ -114,7 +114,7 @@ public override bool CanSeek { get { - return true; + return !this.disposed; } } @@ -126,7 +126,7 @@ public override bool CanWrite { get { - return true; + return !this.disposed; } } diff --git a/Lib/Common/Core/NonCloseableStream.cs b/Lib/Common/Core/NonCloseableStream.cs new file mode 100644 index 000000000..b636083bc --- /dev/null +++ b/Lib/Common/Core/NonCloseableStream.cs @@ -0,0 +1,196 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.Core +{ + using Microsoft.WindowsAzure.Storage.Core.Util; + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + + internal class NonCloseableStream : Stream + { + private readonly Stream wrappedStream; + + /// + /// Initializes a new instance of the NonCloseableStream class This stream ensures that the user stream + /// is not closed even when the enclosing crypto stream is closed in order to flush the final block of data. + /// + /// The stream to wrap. + public NonCloseableStream(Stream wrappedStream) + { + CommonUtility.AssertNotNull("WrappedStream", wrappedStream); + + this.wrappedStream = wrappedStream; + } + + public override bool CanRead + { + get { return this.wrappedStream.CanRead; } + } + + public override bool CanSeek + { + get { return this.wrappedStream.CanSeek; } + } + + public override bool CanTimeout + { + get { return this.wrappedStream.CanTimeout; } + } + + public override bool CanWrite + { + get { return this.wrappedStream.CanWrite; } + } + + public override long Length + { + get { return this.wrappedStream.Length; } + } + + public override long Position + { + get { return this.wrappedStream.Position; } + set { this.wrappedStream.Position = value; } + } + + public override int ReadTimeout + { + get { return this.wrappedStream.ReadTimeout; } + set { this.wrappedStream.ReadTimeout = value; } + } + + public override int WriteTimeout + { + get { return this.wrappedStream.WriteTimeout; } + set { this.wrappedStream.WriteTimeout = value; } + } + + public override void Flush() + { + this.wrappedStream.Flush(); + } + + public override void SetLength(long value) + { + this.wrappedStream.SetLength(value); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return this.wrappedStream.Seek(offset, origin); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return this.wrappedStream.Read(buffer, offset, count); + } + +#if WINDOWS_DESKTOP + /// + /// Begins an asynchronous read operation. + /// + /// When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read. + /// An optional asynchronous callback, to be called when the read is complete. + /// A user-provided object that distinguishes this particular asynchronous read request from other requests. + /// An IAsyncResult that represents the asynchronous read, which could still be pending. + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return this.wrappedStream.BeginRead(buffer, offset, count, callback, state); + } + + /// + /// Waits for the pending asynchronous read to complete. + /// + /// The reference to the pending asynchronous request to finish. + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero if the end of the stream has been reached. + public override int EndRead(IAsyncResult asyncResult) + { + return this.wrappedStream.EndRead(asyncResult); + } +#endif + +#if WINDOWS_RT || ASPNET_K + /// + /// Asynchronously reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// The buffer to write the data into. + /// The byte offset in buffer at which to begin writing data from the stream. + /// The maximum number of bytes to read. + /// The token to monitor for cancellation requests. + /// A task that represents the asynchronous read operation. + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return this.wrappedStream.ReadAsync(buffer, offset, count, cancellationToken); + } +#endif + +#if WINDOWS_DESKTOP + /// + /// Begins an asynchronous write operation. + /// + /// The buffer to write data from. + /// The zero-based byte offset in buffer at which to begin copying bytes to the current stream. + /// The number of bytes to write. + /// An optional asynchronous callback, to be called when the write is complete. + /// A user-provided object that distinguishes this particular asynchronous write request from other requests. + /// An IAsyncResult that represents the asynchronous write, which could still be pending. + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return this.wrappedStream.BeginWrite(buffer, offset, count, callback, state); + } + + /// + /// Ends an asynchronous write operation. + /// + /// The reference to the pending asynchronous request to finish. + public override void EndWrite(IAsyncResult asyncResult) + { + this.wrappedStream.EndWrite(asyncResult); + } +#endif + + public override void Write(byte[] buffer, int offset, int count) + { + this.wrappedStream.Write(buffer, offset, count); + } + +#if WINDOWS_RT || ASPNET_K + /// + /// Asynchronously writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// The buffer to write data from. + /// The zero-based byte offset in buffer from which to begin copying bytes to the stream. + /// The maximum number of bytes to write. + /// The token to monitor for cancellation requests. + /// A task that represents the asynchronous write operation. + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return this.wrappedStream.WriteAsync(buffer, offset, count, cancellationToken); + } +#endif + + protected override void Dispose(bool disposing) + { + // no-op + } + } +} diff --git a/Lib/Common/Core/SR.cs b/Lib/Common/Core/SR.cs index ef0a66a0d..f08b9e58b 100644 --- a/Lib/Common/Core/SR.cs +++ b/Lib/Common/Core/SR.cs @@ -57,14 +57,16 @@ internal class SR public const string Directory = "directory"; public const string EmptyBatchOperation = "Cannot execute an empty batch operation"; public const string EncryptedMessageTooLarge = "Encrypted Messages cannot be larger than {0} bytes. Please note that encrypting queue messages can increase their size."; - public const string EncryptionDataNotPresentError = "Encryption data does not exist. If you do not want to decrypt the data, please do not set the encryption policy on request options."; + public const string EncryptionDataNotPresentError = "Encryption data does not exist. If you do not want to decrypt the data, please do not set the RequireEncryption flag on request options."; public const string EncryptionLogicError = "Encryption logic threw error. Please check the inner exception for more details."; public const string EncryptedMessageDeserializingError = "Error while de-serializing the encrypted queue message string from the wire. Please check inner exception for more details."; public const string EncryptionNotSupportedForOperation = "Encryption is not supported for the current operation. Please ensure that EncryptionPolicy is not set on RequestOptions."; public const string EncryptingNullPropertiesNotAllowed = "Null properties cannot be encrypted. Please assign a default value to the property if you wish to encrypt it."; public const string EncryptionMetadataError = "Error while de-serializing the encryption metadata string from the wire."; public const string EncryptionNotSupportedForExistingBlobs = "Encryption is not supported for a blob that already exists. Please do not specify an encryption policy."; + public const string EncryptionNotSupportedForFiles = "Encryption is not supported for files."; public const string EncryptionNotSupportedForPageBlobsOnPhone = "Encryption is not supported for PageBlobs on Windows Phone."; + public const string EncryptionPolicyMissingInStrictMode = "Encryption Policy is mandatory when RequireEncryption is set to true. If you do not want to encrypt/decrypt data, please set RequireEncryption to false in request options."; public const string EncryptionProtocolVersionInvalid = "Invalid Encryption Agent. This version of the client library does not understand the Encryption Agent set on the blob."; public const string ETagMissingForDelete = "Delete requires an ETag (which may be the '*' wildcard)."; public const string ETagMissingForMerge = "Merge requires an ETag (which may be the '*' wildcard)."; @@ -84,6 +86,7 @@ internal class SR public const string InvalidBlobListItem = "Invalid blob list item returned"; public const string InvalidCorsRule = "A CORS rule must contain at least one allowed origin and allowed method, and MaxAgeInSeconds cannot have a value less than zero."; public const string InvalidDelimiter = "\\ is an invalid delimiter."; + public const string InvalidFileAclType = "Invalid acl public access type returned '{0}'. Expected file or share."; public const string InvalidEncryptionAlgorithm = "Invalid Encryption Algorithm found on the resource. This version of the client library does not support the specified encryption algorithm."; public const string InvalidEncryptionMode = "Invalid BlobEncryptionMode set on the policy. Please set it to FullBlob when the policy is used with UploadFromStream."; public const string InvalidFileListItem = "Invalid file list item returned"; @@ -95,6 +98,7 @@ internal class SR public const string InvalidListingDetails = "Invalid blob listing details specified."; public const string InvalidLoggingLevel = "Invalid logging operations specified."; public const string InvalidMetricsLevel = "Invalid metrics level specified."; + public const string InvalidBlockSize = "Append block data should not exceed the maximum blob size condition value."; public const string InvalidPageSize = "Page data must be a multiple of 512 bytes."; public const string InvalidResourceName = "Invalid {0} name. Check MSDN for more information about valid {0} naming."; public const string InvalidResourceNameLength = "Invalid {0} name length. The {0} name must be between {1} and {2} characters long."; @@ -143,6 +147,7 @@ internal class SR public const string PathStyleUriMissingAccountNameInformation = "Missing account name information inside path style uri. Path style uris should be of the form http:///"; public const string PayloadFormat = "Setting payload format for the request to '{0}'."; public const string PreconditionFailed = "The condition specified using HTTP conditional header(s) is not met."; + public const string PreconditionFailureIgnored = "Pre-condition failure on a retry is being ignored since the request should have succeeded in the first attempt."; public const string PrimaryOnlyCommand = "This operation can only be executed against the primary storage location."; public const string PropertyResolverCacheDisabled = "Property resolver cache is disabled."; public const string PropertyResolverThrewError = "The custom property resolver delegate threw an exception. Check the inner exception for more details"; @@ -172,7 +177,7 @@ internal class SR public const string TableQueryTypeMustHaveDefaultParameterlessCtor = "TableQuery Generic Type must provide a default parameterless constructor."; public const string TakeCountNotPositive = "Take count must be positive and greater than 0."; public const string TimeoutExceptionMessage = "The client could not finish the operation within specified timeout."; - public const string TooManyPolicyIdentifiers = "Too many '{0}' shared access policy identifiers provided. Server does not support setting more than '{1}' on a single container, queue, or table."; + public const string TooManyPolicyIdentifiers = "Too many '{0}' shared access policy identifiers provided. Server does not support setting more than '{1}' on a single container, queue, table, or share."; public const string TooManyPathSegments = "The count of URL path segments (strings between '/' characters) as part of the blob name cannot exceed 254."; public const string TraceAbort = "Aborting pending request due to timeout."; public const string TraceAbortError = "Could not abort pending request because of {0}."; diff --git a/Lib/Common/Core/SyncMemoryStream.cs b/Lib/Common/Core/SyncMemoryStream.cs index 10d97f566..38aeb35d5 100644 --- a/Lib/Common/Core/SyncMemoryStream.cs +++ b/Lib/Common/Core/SyncMemoryStream.cs @@ -25,7 +25,7 @@ namespace Microsoft.WindowsAzure.Storage.Core /// /// This class provides APM Read/Write overrides for memory stream to improve performance. /// - internal class SyncMemoryStream : MemoryStream + public class SyncMemoryStream : MemoryStream { /// /// Initializes a new instance of the SyncMemoryStream class with an expandable capacity initialized to zero. diff --git a/Lib/Common/Core/UriQueryBuilder.cs b/Lib/Common/Core/UriQueryBuilder.cs index a10feac1a..11b228fff 100644 --- a/Lib/Common/Core/UriQueryBuilder.cs +++ b/Lib/Common/Core/UriQueryBuilder.cs @@ -20,6 +20,7 @@ namespace Microsoft.WindowsAzure.Storage.Core using Microsoft.WindowsAzure.Storage.Core.Util; using System; using System.Collections.Generic; + using System.Globalization; using System.Text; /// @@ -71,7 +72,7 @@ public string this[string name] } else { - throw new KeyNotFoundException(string.Format(SR.QueryBuilderKeyNotFound, name)); + throw new KeyNotFoundException(string.Format(CultureInfo.InvariantCulture, SR.QueryBuilderKeyNotFound, name)); } } } diff --git a/Lib/Common/Core/Util/AuthenticationUtility.cs b/Lib/Common/Core/Util/AuthenticationUtility.cs index a38d8e157..04559a6da 100644 --- a/Lib/Common/Core/Util/AuthenticationUtility.cs +++ b/Lib/Common/Core/Util/AuthenticationUtility.cs @@ -60,7 +60,7 @@ public static string GetPreferredDateHeaderValue(HttpRequestMessage request) public static void AppendCanonicalizedContentLengthHeader(CanonicalizedString canonicalizedString, HttpRequestMessage request) { long? contentLength = request.Content.Headers.ContentLength; - if (contentLength.HasValue && contentLength.Value != -1L) + if (contentLength.HasValue && contentLength.Value != -1L && contentLength.Value != 0) { canonicalizedString.AppendCanonicalizedElement(contentLength.Value.ToString(CultureInfo.InvariantCulture)); } @@ -170,7 +170,7 @@ public static string GetPreferredDateHeaderValue(HttpWebRequest request) /// The request where the value is read from. public static void AppendCanonicalizedContentLengthHeader(CanonicalizedString canonicalizedString, HttpWebRequest request) { - if (request.ContentLength != -1L) + if (request.ContentLength != -1L && request.ContentLength != 0) { canonicalizedString.AppendCanonicalizedElement(request.ContentLength.ToString(CultureInfo.InvariantCulture)); } diff --git a/Lib/Common/Core/Util/CommonUtility.cs b/Lib/Common/Core/Util/CommonUtility.cs index d2901b2fe..0c3b34e2d 100644 --- a/Lib/Common/Core/Util/CommonUtility.cs +++ b/Lib/Common/Core/Util/CommonUtility.cs @@ -243,21 +243,18 @@ internal static int RoundUpToSeconds(this TimeSpan timeSpan) } /// - /// Computes an XOR of 2 byte arrays. + /// Appends 2 byte arrays. /// /// First array. /// Second array. /// The result byte array. - internal static byte[] BinaryXor(byte[] arr1, byte[] arr2) + internal static byte[] BinaryAppend(byte[] arr1, byte[] arr2) { - int longLen = Math.Max(arr1.Length, arr2.Length); - byte[] result = new byte[longLen]; - Array.Copy(arr1.Length >= arr2.Length ? arr1 : arr2, result, longLen); - int shortLen = Math.Min(arr1.Length, arr2.Length); - for (int i = 0; i < shortLen; i++) - { - result[i] = (byte)(arr1[i] ^ arr2[i]); - } + int newLen = arr1.Length + arr2.Length; + byte[] result = new byte[newLen]; + + Array.Copy(arr1, result, arr1.Length); + Array.Copy(arr2, 0, result, arr1.Length, arr2.Length); return result; } diff --git a/Lib/Common/Core/Util/NavigationHelper.cs b/Lib/Common/Core/Util/NavigationHelper.cs index f4b234687..c1582fa04 100644 --- a/Lib/Common/Core/Util/NavigationHelper.cs +++ b/Lib/Common/Core/Util/NavigationHelper.cs @@ -23,6 +23,7 @@ namespace Microsoft.WindowsAzure.Storage.Core.Util using System; using System.Collections.Generic; using System.Globalization; + using System.Linq; /// /// Contains methods for dealing with navigation. @@ -140,6 +141,44 @@ internal static string GetFileName(Uri fileAddress, bool? usePathStyleUris) return Uri.UnescapeDataString(fileName); } + /// + /// Retrieves the file and directory part of a storage Uri. + /// + /// The file address. + /// If set to true use path style Uris. + /// The file name including directories. + internal static string GetFileAndDirectoryName(Uri fileAddress, bool? usePathStyleUris) + { + CommonUtility.AssertNotNull("fileAddress", fileAddress); + + if (usePathStyleUris == null) + { + // Automatically determine whether to use path style vs host style uris + usePathStyleUris = CommonUtility.UsePathStyleAddressing(fileAddress); + } + + string[] addressParts = fileAddress.Segments; + int shareIndex = usePathStyleUris.Value ? 2 : 1; + + if (addressParts.Length - 1 < shareIndex) + { + // No reference appears to any share or file. + string error = string.Format(CultureInfo.CurrentCulture, SR.MissingShareInformation, fileAddress); + throw new ArgumentException(error, "fileAddress"); + } + else if (addressParts.Length - 1 == shareIndex) + { + // This is root directory of a share. + return string.Empty; + } + else + { + // This is a file with directories (the relevant case). + // Skip (shareIndex + 1) because Skip takes a count, not an index, as a param. + return Uri.UnescapeDataString(string.Concat(addressParts.Skip(shareIndex + 1))); + } + } + /// /// Retrieves the parent name from a storage Uri. /// @@ -694,7 +733,91 @@ private static Uri ParseBlobQueryAndVerify(Uri address, out StorageCredentials p } } - parsedCredentials = SharedAccessSignatureHelper.ParseQuery(queryParameters, true); + parsedCredentials = SharedAccessSignatureHelper.ParseQuery(queryParameters); + + // SAS credentials were passed in the Uri if parsedCredentials is non null. + if (parsedCredentials != null) + { + string signedResource; + queryParameters.TryGetValue(Constants.QueryConstants.SignedResource, out signedResource); + + if (string.IsNullOrEmpty(signedResource)) + { + string errorMessage = string.Format(CultureInfo.CurrentCulture, SR.MissingMandatoryParametersForSAS); + throw new ArgumentException(errorMessage); + } + } + + return new Uri(address.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped)); + } + + /// + /// Parse Uri for SAS (Shared access signature) information. + /// + /// The complete Uri. + /// The credentials to use. + /// The file URI without credentials info + /// address + /// + /// Validate that no other query parameters are passed in. + /// Any SAS information will be recorded as corresponding credentials instance. + /// If credentials is passed in and it does not match the SAS information found, an + /// exception will be thrown. + /// Otherwise a new client is created based on SAS information or as anonymous credentials. + /// + internal static StorageUri ParseFileQueryAndVerify(StorageUri address, out StorageCredentials parsedCredentials) + { + StorageCredentials secondaryCredentials; + return new StorageUri( + ParseFileQueryAndVerify(address.PrimaryUri, out parsedCredentials), + ParseFileQueryAndVerify(address.SecondaryUri, out secondaryCredentials)); + } + + /// + /// Parse Uri for SAS (Shared access signature) information. + /// + /// The complete Uri. + /// The credentials to use. + /// The file URI without credentials info + /// address + /// + /// Validate that no other query parameters are passed in. + /// Any SAS information will be recorded as corresponding credentials instance. + /// If credentials is passed in and it does not match the SAS information found, an + /// exception will be thrown. + /// Otherwise a new client is created based on SAS information or as anonymous credentials. + /// + private static Uri ParseFileQueryAndVerify(Uri address, out StorageCredentials parsedCredentials) + { + parsedCredentials = null; + if (address == null) + { + return null; + } + + if (!address.IsAbsoluteUri) + { + string errorMessage = string.Format(CultureInfo.CurrentCulture, SR.RelativeAddressNotPermitted, address.ToString()); + throw new ArgumentException(errorMessage, "address"); + } + + IDictionary queryParameters = HttpWebUtility.ParseQueryString(address.Query); + + parsedCredentials = SharedAccessSignatureHelper.ParseQuery(queryParameters); + + // SAS credentials were passed in the Uri if parsedCredentials is non null. + if (parsedCredentials != null) + { + string signedResource; + queryParameters.TryGetValue(Constants.QueryConstants.SignedResource, out signedResource); + + if (string.IsNullOrEmpty(signedResource)) + { + string errorMessage = string.Format(CultureInfo.CurrentCulture, SR.MissingMandatoryParametersForSAS); + throw new ArgumentException(errorMessage); + } + } + return new Uri(address.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped)); } @@ -746,7 +869,7 @@ private static Uri ParseQueueTableQueryAndVerify(Uri address, out StorageCredent IDictionary queryParameters = HttpWebUtility.ParseQueryString(address.Query); - parsedCredentials = SharedAccessSignatureHelper.ParseQuery(queryParameters, false); + parsedCredentials = SharedAccessSignatureHelper.ParseQuery(queryParameters); return new Uri(address.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped)); } diff --git a/Lib/Common/File/CloudFile.Common.cs b/Lib/Common/File/CloudFile.Common.cs index 45d7e89ae..c8f3a8583 100644 --- a/Lib/Common/File/CloudFile.Common.cs +++ b/Lib/Common/File/CloudFile.Common.cs @@ -18,10 +18,15 @@ namespace Microsoft.WindowsAzure.Storage.File { using Microsoft.WindowsAzure.Storage.Auth; + using Microsoft.WindowsAzure.Storage.Blob; + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Core.Auth; using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.File.Protocol; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Collections.Generic; + using System.Globalization; /// /// Represents a Windows Azure File. @@ -38,6 +43,15 @@ public sealed partial class CloudFile : IListFileItem /// private int streamMinimumReadSizeInBytes = Constants.DefaultWriteBlockSizeBytes; + /// + /// Initializes a new instance of the class using an absolute URI to the file. + /// + /// The absolute URI to the file. + public CloudFile(Uri fileAbsoluteUri) + : this(fileAbsoluteUri, null /* credentials */) + { + } + /// /// Initializes a new instance of the class using an absolute URI to the file. /// @@ -207,6 +221,18 @@ public StorageUri StorageUri } } + /// + /// Gets the state of the most recent or pending copy operation. + /// + /// A object containing the copy state, or null if there is no copy state for the file. + public CopyState CopyState + { + get + { + return this.attributes.CopyState; + } + } + /// /// Gets the file's name. /// @@ -254,6 +280,82 @@ public CloudFileDirectory Parent } } + /// + /// Returns a shared access signature for the file. + /// + /// A object specifying the access policy for the shared access signature. + /// A shared access signature, as a URI query string. + /// The query string returned includes the leading question mark. + public string GetSharedAccessSignature(SharedAccessFilePolicy policy) + { + return this.GetSharedAccessSignature(policy, null /* headers */, null /* groupPolicyIdentifier */); + } + + /// + /// Returns a shared access signature for the file. + /// + /// A object specifying the access policy for the shared access signature. + /// A string identifying a stored access policy. + /// A shared access signature, as a URI query string. + /// The query string returned includes the leading question mark. +#if WINDOWS_RT + [Windows.Foundation.Metadata.DefaultOverload] +#endif + public string GetSharedAccessSignature(SharedAccessFilePolicy policy, string groupPolicyIdentifier) + { + return this.GetSharedAccessSignature(policy, null /* headers */, groupPolicyIdentifier); + } + + /// + /// Returns a shared access signature for the file. + /// + /// A object specifying the access policy for the shared access signature. + /// A object specifying optional header values to set for a file accessed with this SAS. + /// A shared access signature, as a URI query string. + public string GetSharedAccessSignature(SharedAccessFilePolicy policy, SharedAccessFileHeaders headers) + { + return this.GetSharedAccessSignature(policy, headers, null /* groupPolicyIdentifier */); + } + + /// + /// Returns a shared access signature for the file. + /// + /// A object specifying the access policy for the shared access signature. + /// A object specifying optional header values to set for a file accessed with this SAS. + /// A string identifying a stored access policy. + /// A shared access signature, as a URI query string. + public string GetSharedAccessSignature(SharedAccessFilePolicy policy, SharedAccessFileHeaders headers, string groupPolicyIdentifier) + { + if (!this.ServiceClient.Credentials.IsSharedKey) + { + string errorMessage = string.Format(CultureInfo.InvariantCulture, SR.CannotCreateSASWithoutAccountKey); + throw new InvalidOperationException(errorMessage); + } + + string resourceName = this.GetCanonicalName(); + StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; + string signature = SharedAccessSignatureHelper.GetHash(policy, headers, groupPolicyIdentifier, resourceName, Constants.HeaderConstants.TargetStorageVersion, accountKey.KeyValue); + + UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature(policy, headers, groupPolicyIdentifier, "f", signature, accountKey.KeyName, Constants.HeaderConstants.TargetStorageVersion); + + return builder.ToString(); + } + + /// + /// Gets the canonical name of the file, formatted as file/<account-name>/<share-name>/<directory-name>/<file-name>. + /// This is used by both Shared Access and Copy operations. + /// + /// The canonical name of the file. + private string GetCanonicalName() + { + string accountName = this.ServiceClient.Credentials.AccountName; + string shareName = this.Share.Name; + + // Replace \ with / for uri compatibility when running under .net 4.5. + string fileAndDirectoryName = NavigationHelper.GetFileAndDirectoryName(this.Uri, this.ServiceClient.UsePathStyleUris).Replace('\\', '/'); + return string.Format(CultureInfo.InvariantCulture, "/{0}/{1}/{2}/{3}", SR.File, accountName, shareName, fileAndDirectoryName); + } + /// /// Parse URI. /// @@ -261,10 +363,18 @@ public CloudFileDirectory Parent /// The credentials to use. private void ParseQueryAndVerify(StorageUri address, StorageCredentials credentials) { - this.attributes.StorageUri = address; + StorageCredentials parsedCredentials; + this.attributes.StorageUri = NavigationHelper.ParseFileQueryAndVerify(address, out parsedCredentials); + + if (parsedCredentials != null && credentials != null) + { + string error = string.Format(CultureInfo.CurrentCulture, SR.MultipleCredentialsProvided); + throw new ArgumentException(error); + } + if (this.ServiceClient == null) { - this.ServiceClient = new CloudFileClient(NavigationHelper.GetServiceClientBaseAddress(this.StorageUri, null /* usePathStyleUris */), credentials); + this.ServiceClient = new CloudFileClient(NavigationHelper.GetServiceClientBaseAddress(this.StorageUri, null /* usePathStyleUris */), credentials ?? parsedCredentials); } this.Name = NavigationHelper.GetFileName(this.Uri, this.ServiceClient.UsePathStyleUris); diff --git a/Lib/Common/File/CloudFileAttributes.cs b/Lib/Common/File/CloudFileAttributes.cs index 68b4dfa57..078fcb68f 100644 --- a/Lib/Common/File/CloudFileAttributes.cs +++ b/Lib/Common/File/CloudFileAttributes.cs @@ -17,8 +17,11 @@ namespace Microsoft.WindowsAzure.Storage.File { + using Microsoft.WindowsAzure.Storage.Blob; + using Microsoft.WindowsAzure.Storage.Core; using System; using System.Collections.Generic; + using System.Globalization; internal sealed class CloudFileAttributes { @@ -57,5 +60,11 @@ public Uri Uri /// /// The list of URIs for all locations. public StorageUri StorageUri { get; internal set; } + + /// + /// Gets the state of the most recent or pending copy operation. + /// + /// A object containing the copy state, or null if no copy file state exists for this file. + public CopyState CopyState { get; internal set; } } } diff --git a/Lib/Common/File/CloudFileDirectory.Common.cs b/Lib/Common/File/CloudFileDirectory.Common.cs index 9df1cda10..1fe66b17e 100644 --- a/Lib/Common/File/CloudFileDirectory.Common.cs +++ b/Lib/Common/File/CloudFileDirectory.Common.cs @@ -21,7 +21,10 @@ namespace Microsoft.WindowsAzure.Storage.File using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.File.Protocol; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; + using System.Collections.Generic; + using System.Globalization; /// /// Represents a directory of files, designated by a delimiter character. @@ -39,6 +42,15 @@ public sealed partial class CloudFileDirectory : IListFileItem /// private CloudFileDirectory parent; + /// + /// Initializes a new instance of the class using an absolute URI to the directory. + /// + /// A object containing the absolute URI to the directory. + public CloudFileDirectory(Uri directoryAbsoluteUri) + : this(new StorageUri(directoryAbsoluteUri), null /* StorageCredentials */) + { + } + /// /// Initializes a new instance of the class using an absolute URI to the directory. /// @@ -66,6 +78,7 @@ internal CloudFileDirectory(StorageUri directoryAbsoluteUri, StorageCredentials public CloudFileDirectory(StorageUri directoryAbsoluteUri, StorageCredentials credentials) #endif { + this.Metadata = new Dictionary(); this.Properties = new FileDirectoryProperties(); this.ParseQueryAndVerify(directoryAbsoluteUri, credentials); } @@ -82,6 +95,7 @@ internal CloudFileDirectory(StorageUri uri, string directoryName, CloudFileShare CommonUtility.AssertNotNull("directoryName", directoryName); CommonUtility.AssertNotNull("share", share); + this.Metadata = new Dictionary(); this.Properties = new FileDirectoryProperties(); this.StorageUri = uri; this.ServiceClient = share.ServiceClient; @@ -119,6 +133,12 @@ public Uri Uri /// A object. public FileDirectoryProperties Properties { get; internal set; } + /// + /// Gets the user-defined metadata for the directory. + /// + /// The directory's metadata, as a collection of name-value pairs. + public IDictionary Metadata { get; internal set; } + /// /// Gets a object that represents the share for the directory. /// @@ -224,8 +244,20 @@ public CloudFileDirectory GetDirectoryReference(string itemName) /// The credentials to use. private void ParseQueryAndVerify(StorageUri address, StorageCredentials credentials) { - this.StorageUri = address; - this.ServiceClient = new CloudFileClient(NavigationHelper.GetServiceClientBaseAddress(this.StorageUri, null /* usePathStyleUris */), credentials); + StorageCredentials parsedCredentials; + this.StorageUri = NavigationHelper.ParseFileQueryAndVerify(address, out parsedCredentials); + + if (parsedCredentials != null && credentials != null) + { + string error = string.Format(CultureInfo.CurrentCulture, SR.MultipleCredentialsProvided); + throw new ArgumentException(error); + } + + if (this.ServiceClient == null) + { + this.ServiceClient = new CloudFileClient(NavigationHelper.GetServiceClientBaseAddress(this.StorageUri, null /* usePathStyleUris */), credentials ?? parsedCredentials); + } + this.Name = NavigationHelper.GetFileName(this.Uri, this.ServiceClient.UsePathStyleUris); } } diff --git a/Lib/Common/File/CloudFileShare.Common.cs b/Lib/Common/File/CloudFileShare.Common.cs index 3cd1605ce..6566bc4f0 100644 --- a/Lib/Common/File/CloudFileShare.Common.cs +++ b/Lib/Common/File/CloudFileShare.Common.cs @@ -18,15 +18,29 @@ namespace Microsoft.WindowsAzure.Storage.File { using Microsoft.WindowsAzure.Storage.Auth; + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Core.Auth; using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.File.Protocol; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Collections.Generic; + using System.Globalization; /// /// Represents a share in the Windows Azure File service. /// public sealed partial class CloudFileShare { + /// + /// Initializes a new instance of the class. + /// + /// The absolute URI to the share. + public CloudFileShare(Uri shareAddress) + : this(shareAddress, null /* credentials */) + { + } + /// /// Initializes a new instance of the class. /// @@ -52,11 +66,11 @@ public static CloudFileShare Create(StorageUri shareAddress, StorageCredentials internal CloudFileShare(StorageUri shareAddress, StorageCredentials credentials) #else public CloudFileShare(StorageUri shareAddress, StorageCredentials credentials) -#endif +#endif { CommonUtility.AssertNotNull("shareAddress", shareAddress); CommonUtility.AssertNotNull("shareAddress", shareAddress.PrimaryUri); - + this.ParseQueryAndVerify(shareAddress, credentials); this.Metadata = new Dictionary(); this.Properties = new FileShareProperties(); @@ -111,7 +125,7 @@ public Uri Uri /// /// The list of URIs for all locations. public StorageUri StorageUri { get; private set; } - + /// /// Gets the name of the share. /// @@ -130,6 +144,54 @@ public Uri Uri /// The share's properties. public FileShareProperties Properties { get; private set; } + /// + /// Returns the canonical name for shared access. + /// + /// The canonical name. + private string GetSharedAccessCanonicalName() + { + string accountName = this.ServiceClient.Credentials.AccountName; + string shareName = this.Name; + + return string.Format(CultureInfo.InvariantCulture, "/{0}/{1}/{2}", SR.File, accountName, shareName); + } + + /// + /// Returns a shared access signature for the share. + /// + /// A object specifying the access policy for the shared access signature. + /// A shared access signature, as a URI query string. + /// The query string returned includes the leading question mark. + public string GetSharedAccessSignature(SharedAccessFilePolicy policy) + { + return this.GetSharedAccessSignature(policy, null /* groupPolicyIdentifier */); + } + + /// + /// Returns a shared access signature for the share. + /// + /// A object specifying the access policy for the shared access signature. + /// A share-level access policy. + /// A shared access signature, as a URI query string. + /// The query string returned includes the leading question mark. + public string GetSharedAccessSignature(SharedAccessFilePolicy policy, string groupPolicyIdentifier) + { + if (!this.ServiceClient.Credentials.IsSharedKey) + { + string errorMessage = string.Format(CultureInfo.CurrentCulture, SR.CannotCreateSASWithoutAccountKey); + throw new InvalidOperationException(errorMessage); + } + + string resourceName = this.GetSharedAccessCanonicalName(); + StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; + string signature = SharedAccessSignatureHelper.GetHash(policy, null /* headers */, groupPolicyIdentifier, resourceName, Constants.HeaderConstants.TargetStorageVersion, accountKey.KeyValue); + string accountKeyName = accountKey.KeyName; + + UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature(policy, null /* headers */, groupPolicyIdentifier, "s", signature, accountKeyName, Constants.HeaderConstants.TargetStorageVersion); + + return builder.ToString(); + } + /// /// Parse URI for SAS (Shared Access Signature) information. /// @@ -137,8 +199,20 @@ public Uri Uri /// The credentials to use. private void ParseQueryAndVerify(StorageUri address, StorageCredentials credentials) { - this.StorageUri = address; - this.ServiceClient = new CloudFileClient(NavigationHelper.GetServiceClientBaseAddress(this.StorageUri, null), credentials); + StorageCredentials parsedCredentials; + this.StorageUri = NavigationHelper.ParseFileQueryAndVerify(address, out parsedCredentials); + + if (parsedCredentials != null && credentials != null) + { + string error = string.Format(CultureInfo.CurrentCulture, SR.MultipleCredentialsProvided); + throw new ArgumentException(error); + } + + if (this.ServiceClient == null) + { + this.ServiceClient = new CloudFileClient(NavigationHelper.GetServiceClientBaseAddress(this.StorageUri, null /* usePathStyleUris */), credentials ?? parsedCredentials); + } + this.Name = NavigationHelper.GetShareNameFromShareAddress(this.Uri, this.ServiceClient.UsePathStyleUris); } diff --git a/Lib/Common/File/FileRequestOptions.cs b/Lib/Common/File/FileRequestOptions.cs index 137cfcaaa..06dd2a7fc 100644 --- a/Lib/Common/File/FileRequestOptions.cs +++ b/Lib/Common/File/FileRequestOptions.cs @@ -57,6 +57,10 @@ internal FileRequestOptions(FileRequestOptions other) if (other != null) { this.RetryPolicy = other.RetryPolicy; + +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + this.RequireEncryption = other.RequireEncryption; +#endif this.LocationMode = other.LocationMode; this.ServerTimeout = other.ServerTimeout; this.MaximumExecutionTime = other.MaximumExecutionTime; @@ -76,6 +80,10 @@ internal static FileRequestOptions ApplyDefaults(FileRequestOptions options, Clo modifiedOptions.LocationMode = (modifiedOptions.LocationMode ?? serviceClient.DefaultRequestOptions.LocationMode) ?? RetryPolicies.LocationMode.PrimaryOnly; +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + modifiedOptions.RequireEncryption = modifiedOptions.RequireEncryption ?? serviceClient.DefaultRequestOptions.RequireEncryption; +#endif + modifiedOptions.ServerTimeout = modifiedOptions.ServerTimeout ?? serviceClient.DefaultRequestOptions.ServerTimeout; modifiedOptions.MaximumExecutionTime = modifiedOptions.MaximumExecutionTime ?? serviceClient.DefaultRequestOptions.MaximumExecutionTime; modifiedOptions.ParallelOperationThreadCount = (modifiedOptions.ParallelOperationThreadCount @@ -159,6 +167,28 @@ public LocationMode? LocationMode } } +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + /// + /// Gets or sets a value to indicate whether data written and read by the client library should be encrypted. + /// + /// Use true to specify that data should be encrypted/decrypted for all transactions; otherwise, false. + public bool? RequireEncryption + { + get + { + return false; + } + + set + { + if (value.HasValue && value.Value) + { + throw new NotSupportedException(SR.EncryptionNotSupportedForFiles); + } + } + } +#endif + /// /// Gets or sets the server timeout interval for the request. /// diff --git a/Lib/Common/File/FileSharePermissions.cs b/Lib/Common/File/FileSharePermissions.cs new file mode 100644 index 000000000..540d7854e --- /dev/null +++ b/Lib/Common/File/FileSharePermissions.cs @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File +{ + /// + /// Represents the permissions for a share. + /// + public sealed class FileSharePermissions + { + /// + /// Initializes a new instance of the class. + /// + public FileSharePermissions() + { + this.SharedAccessPolicies = new SharedAccessFilePolicies(); + } + + /// + /// Gets the set of shared access policies for the share. + /// + /// A object. + public SharedAccessFilePolicies SharedAccessPolicies { get; private set; } + } +} diff --git a/Lib/Common/File/FileShareProperties.cs b/Lib/Common/File/FileShareProperties.cs index 1536907ae..8870a6d5c 100644 --- a/Lib/Common/File/FileShareProperties.cs +++ b/Lib/Common/File/FileShareProperties.cs @@ -17,6 +17,8 @@ namespace Microsoft.WindowsAzure.Storage.File { + using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; /// @@ -24,6 +26,8 @@ namespace Microsoft.WindowsAzure.Storage.File /// public sealed class FileShareProperties { + private int? quota; + /// /// Gets the ETag value for the share. /// @@ -35,5 +39,26 @@ public sealed class FileShareProperties /// /// The share's last-modified time. public DateTimeOffset? LastModified { get; internal set; } + + /// + /// Gets or sets the maximum size for the share, in gigabytes. + /// + public int? Quota + { + get + { + return this.quota; + } + + set + { + if (value.HasValue) + { + CommonUtility.AssertInBounds("Quota", value.Value, 1); + } + + this.quota = value; + } + } } } diff --git a/Lib/Common/File/FileSharePublicAccessType.cs b/Lib/Common/File/FileSharePublicAccessType.cs new file mode 100644 index 000000000..30f08de05 --- /dev/null +++ b/Lib/Common/File/FileSharePublicAccessType.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File +{ + /// + /// Specifies the level of public access that is allowed on the share. + /// + public enum FileSharePublicAccessType + { + /// + /// No public access. Only the account owner can read resources in this share. + /// + Off + } +} diff --git a/Lib/Common/File/Protocol/FileAccessPolicyResponse.cs b/Lib/Common/File/Protocol/FileAccessPolicyResponse.cs new file mode 100644 index 000000000..636c7fca4 --- /dev/null +++ b/Lib/Common/File/Protocol/FileAccessPolicyResponse.cs @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File.Protocol +{ + using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; + using System.IO; + using System.Xml.Linq; + + /// + /// Parses the response XML from an operation to set the access policy for a share. + /// + internal class FileAccessPolicyResponse : AccessPolicyResponseBase + { + /// + /// Initializes a new instance of the FileAccessPolicyResponse class. + /// + /// The stream to be parsed. + internal FileAccessPolicyResponse(Stream stream) + : base(stream) + { + } + + /// + /// Parses the current element. + /// + /// The shared access policy element to parse. + /// The shared access policy. + protected override SharedAccessFilePolicy ParseElement(XElement accessPolicyElement) + { + CommonUtility.AssertNotNull("accessPolicyElement", accessPolicyElement); + + SharedAccessFilePolicy accessPolicy = new SharedAccessFilePolicy(); + string sharedAccessStartTimeString = (string)accessPolicyElement.Element(Constants.Start); + if (!string.IsNullOrEmpty(sharedAccessStartTimeString)) + { + accessPolicy.SharedAccessStartTime = Uri.UnescapeDataString(sharedAccessStartTimeString).ToUTCTime(); + } + + string sharedAccessExpiryTimeString = (string)accessPolicyElement.Element(Constants.Expiry); + if (!string.IsNullOrEmpty(sharedAccessExpiryTimeString)) + { + accessPolicy.SharedAccessExpiryTime = Uri.UnescapeDataString(sharedAccessExpiryTimeString).ToUTCTime(); + } + + string permissionsString = (string)accessPolicyElement.Element(Constants.Permission); + if (!string.IsNullOrEmpty(permissionsString)) + { + accessPolicy.Permissions = SharedAccessFilePolicy.PermissionsFromString(permissionsString); + } + + return accessPolicy; + } + } +} diff --git a/Lib/Common/File/Protocol/FileHttpResponseParsers.Common.cs b/Lib/Common/File/Protocol/FileHttpResponseParsers.Common.cs index d01d83ffc..1e192b437 100644 --- a/Lib/Common/File/Protocol/FileHttpResponseParsers.Common.cs +++ b/Lib/Common/File/Protocol/FileHttpResponseParsers.Common.cs @@ -22,6 +22,8 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol { using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System.IO; + using System.Xml; + using System.Xml.Linq; #if WINDOWS_RT internal @@ -30,6 +32,21 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol #endif static partial class FileHttpResponseParsers { + /// + /// Reads service properties from a stream. + /// + /// The stream from which to read the service properties. + /// The service properties stored in the stream. + public static FileServiceProperties ReadServiceProperties(Stream inputStream) + { + using (XmlReader reader = XmlReader.Create(inputStream)) + { + XDocument servicePropertyDocument = XDocument.Load(reader); + + return FileServiceProperties.FromServiceXml(servicePropertyDocument); + } + } + /// /// Reads service stats from a stream. /// diff --git a/Lib/Common/File/Protocol/FileRequest.cs b/Lib/Common/File/Protocol/FileRequest.cs new file mode 100644 index 000000000..d4283512a --- /dev/null +++ b/Lib/Common/File/Protocol/FileRequest.cs @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File.Protocol +{ + using Microsoft.WindowsAzure.Storage.Core.Auth; + using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Text; + using System.Xml; + + /// + /// Provides a set of helper methods for constructing a request against the File service. + /// +#if WINDOWS_RT + internal +#else + public +#endif + static class FileRequest + { + /// + /// Writes a collection of shared access policies to the specified stream in XML format. + /// + /// A collection of shared access policies. + /// An output stream. + public static void WriteSharedAccessIdentifiers(SharedAccessFilePolicies sharedAccessPolicies, Stream outputStream) + { + Request.WriteSharedAccessIdentifiers( + sharedAccessPolicies, + outputStream, + (policy, writer) => + { + writer.WriteElementString( + Constants.Start, + SharedAccessSignatureHelper.GetDateTimeOrEmpty(policy.SharedAccessStartTime)); + writer.WriteElementString( + Constants.Expiry, + SharedAccessSignatureHelper.GetDateTimeOrEmpty(policy.SharedAccessExpiryTime)); + writer.WriteElementString( + Constants.Permission, + SharedAccessFilePolicy.PermissionsToString(policy.Permissions)); + }); + } + } +} diff --git a/Lib/Common/File/Protocol/FileServiceProperties.cs b/Lib/Common/File/Protocol/FileServiceProperties.cs new file mode 100644 index 000000000..20245769b --- /dev/null +++ b/Lib/Common/File/Protocol/FileServiceProperties.cs @@ -0,0 +1,102 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File.Protocol +{ + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml; + using System.Xml.Linq; + + /// + /// Class representing a set of properties pertaining to the Azure File service. + /// + public sealed class FileServiceProperties + { + private ServiceProperties serviceProperties; + + /// + /// Initializes a new instance of the class. + /// + public FileServiceProperties() + { + this.serviceProperties = new ServiceProperties(); + this.serviceProperties.HourMetrics = null; + this.serviceProperties.MinuteMetrics = null; + this.serviceProperties.Logging = null; + } + + /// + /// Gets or sets the Cross Origin Resource Sharing (CORS) properties for the File service. + /// + /// The CORS properties. + public CorsProperties Cors + { + get + { + return this.serviceProperties.Cors; + } + + set + { + this.serviceProperties.Cors = value; + } + } + + /// + /// Constructs a ServiceProperties object from an XML document received from the service. + /// + /// The XML document. + /// A ServiceProperties object containing the properties in the XML document. + internal static FileServiceProperties FromServiceXml(XDocument servicePropertiesDocument) + { + XElement servicePropertiesElement = servicePropertiesDocument.Element(ServiceProperties.StorageServicePropertiesName); + + FileServiceProperties properties = new FileServiceProperties + { + Cors = ServiceProperties.ReadCorsPropertiesFromXml(servicePropertiesElement.Element(ServiceProperties.CorsName)) + }; + + return properties; + } + + /// + /// Converts these properties into XML for communicating with the service. + /// + /// An XML document containing the service properties. + internal XDocument ToServiceXml() + { + return this.serviceProperties.ToServiceXml(); + } + + /// + /// Writes service properties to a stream, formatted in XML. + /// + /// The stream to which the formatted properties are to be written. + internal void WriteServiceProperties(Stream outputStream) + { + this.serviceProperties.WriteServiceProperties(outputStream); + } + } +} diff --git a/Lib/Common/File/Protocol/ListSharesResponse.cs b/Lib/Common/File/Protocol/ListSharesResponse.cs index 7bed1a1d4..59930c03c 100644 --- a/Lib/Common/File/Protocol/ListSharesResponse.cs +++ b/Lib/Common/File/Protocol/ListSharesResponse.cs @@ -210,6 +210,10 @@ private FileShareEntry ParseShareEntry(Uri baseUri) shareProperties.ETag = reader.ReadElementContentAsString(); break; + case Constants.QuotaElement: + shareProperties.Quota = reader.ReadElementContentAsInt(); + break; + default: reader.Skip(); break; diff --git a/Lib/Common/File/Protocol/ShareStats.cs b/Lib/Common/File/Protocol/ShareStats.cs new file mode 100644 index 000000000..c111d0a0b --- /dev/null +++ b/Lib/Common/File/Protocol/ShareStats.cs @@ -0,0 +1,66 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File.Protocol +{ + using System.Globalization; + using System.Xml.Linq; + + /// + /// Class representing a set of stats pertaining to a File Share. + /// + public sealed class ShareStats + { + /// + /// The name of the root XML element. + /// + private const string ShareStatsName = "ShareStats"; + + /// + /// The name of the share usage XML element. + /// + private const string ShareUsageName = "ShareUsage"; + + /// + /// Initializes a new instance of the ServiceStats class. + /// + private ShareStats() + { + } + + /// + /// Gets or sets the share usage. + /// + /// The share usage, in GB. + public int Usage { get; private set; } + + /// + /// Constructs a ShareStats object from an XML document received from the service. + /// + /// The XML document. + /// A ShareStats object containing the properties in the XML document. + internal static ShareStats FromServiceXml(XDocument shareStatsDocument) + { + XElement shareStatsElement = shareStatsDocument.Element(ShareStatsName); + + return new ShareStats() + { + Usage = int.Parse(shareStatsElement.Element(ShareUsageName).Value, CultureInfo.InvariantCulture), + }; + } + } +} diff --git a/Lib/Common/File/ShareListingDetails.cs b/Lib/Common/File/ShareListingDetails.cs index 86771010c..3aaed78de 100644 --- a/Lib/Common/File/ShareListingDetails.cs +++ b/Lib/Common/File/ShareListingDetails.cs @@ -38,6 +38,6 @@ public enum ShareListingDetails /// /// Retrieve all available details. /// - All = 0x1 + All = Metadata } } diff --git a/Lib/Common/File/SharedAccessFileHeaders.cs b/Lib/Common/File/SharedAccessFileHeaders.cs new file mode 100644 index 000000000..a4c677b65 --- /dev/null +++ b/Lib/Common/File/SharedAccessFileHeaders.cs @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File +{ + using Microsoft.WindowsAzure.Storage.Core.Util; + + /// + /// Represents the optional headers that can be returned with files accessed using SAS. + /// + public sealed class SharedAccessFileHeaders + { + /// + /// Initializes a new instance of the class. + /// + public SharedAccessFileHeaders() + { + } + + /// + /// Initializes a new instance of the class based on an existing instance. + /// + /// The set of to clone. + public SharedAccessFileHeaders(SharedAccessFileHeaders sharedAccessFileHeaders) + { + CommonUtility.AssertNotNull("sharedAccessFileHeaders", sharedAccessFileHeaders); + + this.ContentType = sharedAccessFileHeaders.ContentType; + this.ContentDisposition = sharedAccessFileHeaders.ContentDisposition; + this.ContentEncoding = sharedAccessFileHeaders.ContentEncoding; + this.ContentLanguage = sharedAccessFileHeaders.ContentLanguage; + this.CacheControl = sharedAccessFileHeaders.CacheControl; + } + + /// + /// Gets or sets the cache-control header returned with the file. + /// + /// A string containing the cache-control value. + public string CacheControl { get; set; } + + /// + /// Gets or sets the content-disposition header returned with the file. + /// + /// A string containing the content-disposition value. + public string ContentDisposition { get; set; } + + /// + /// Gets or sets the content-encoding header returned with the file. + /// + /// A string containing the content-encoding value. + public string ContentEncoding { get; set; } + + /// + /// Gets or sets the content-language header returned with the file. + /// + /// A string containing the content-language value. + public string ContentLanguage { get; set; } + + /// + /// Gets or sets the content-type header returned with the file. + /// + /// A string containing the content-type value. + public string ContentType { get; set; } + } +} \ No newline at end of file diff --git a/Lib/Common/File/SharedAccessFilePermissions.cs b/Lib/Common/File/SharedAccessFilePermissions.cs new file mode 100644 index 000000000..0eddc7a6c --- /dev/null +++ b/Lib/Common/File/SharedAccessFilePermissions.cs @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File +{ + using System; + + /// + /// Specifies the set of possible permissions for a shared access policy. + /// + [Flags] + public enum SharedAccessFilePermissions + { + /// + /// No shared access granted. + /// + None = 0x0, + + /// + /// Read access granted. + /// + Read = 0x1, + + /// + /// Write access granted. + /// + Write = 0x2, + + /// + /// Delete access granted for files. + /// + Delete = 0x4, + + /// + /// List access granted. + /// + List = 0x8 + } +} \ No newline at end of file diff --git a/Lib/Common/File/SharedAccessFilePolicies.cs b/Lib/Common/File/SharedAccessFilePolicies.cs new file mode 100644 index 000000000..37771e5e5 --- /dev/null +++ b/Lib/Common/File/SharedAccessFilePolicies.cs @@ -0,0 +1,235 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File +{ + using Microsoft.WindowsAzure.Storage.Core.Util; + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + + /// + /// Represents the collection of shared access policies defined for a share. + /// + [SuppressMessage( + "Microsoft.Naming", + "CA1710:IdentifiersShouldHaveCorrectSuffix", + Justification = "Public APIs should not expose base collection types.")] + public sealed class SharedAccessFilePolicies : IDictionary + { + private Dictionary policies = + new Dictionary(); + + /// + /// Adds the specified key and value to the collection of shared access policies. + /// + /// The key of the value to add. + /// The value to add the collection of shared access policies. + public void Add(string key, SharedAccessFilePolicy value) + { + this.policies.Add(key, value); + } + + /// + /// Determines whether the collection of shared access policies contains the specified key. + /// + /// The key to locate in the collection of shared access policies. + /// true if the collection of shared access policies contains an element with the specified key; otherwise, false. + public bool ContainsKey(string key) + { + return this.policies.ContainsKey(key); + } + + /// + /// Gets a collection containing the keys in the shared access policies collection. + /// + /// A collection of strings containing the keys of the shared access policies collection. + public ICollection Keys + { + get + { + return this.policies.Keys; + } + } + + /// + /// Removes the value with the specified key from the shared access policies collection. + /// + /// A string containing the key of the item to remove. + /// true if the element is successfully found and removed; otherwise, false. This method returns false if the key is not found. + public bool Remove(string key) + { + return this.policies.Remove(key); + } + + /// + /// Gets the item associated with the specified key. + /// + /// A string containing the key of the value to get. + /// The item to get. + /// The item associated with the specified key, if the key is found; otherwise, the default value for the type. + public bool TryGetValue(string key, out SharedAccessFilePolicy value) + { + return this.policies.TryGetValue(key, out value); + } + + /// + /// Gets a collection containing the values in the shared access policies collection. + /// + /// A collection of items in the shared access policies collection. + public ICollection Values + { + get + { + return this.policies.Values; + } + } + + /// + /// Gets or sets the item associated with the specified key. + /// + /// A string containing the key of the value to get or set. + /// The item associated with the specified key, or null if key is not in the shared access policies collection. + public SharedAccessFilePolicy this[string key] + { + get + { + return this.policies[key]; + } + + set + { + this.policies[key] = value; + } + } + + /// + /// Adds the specified key/ value, stored in a , to the collection of shared access policies. + /// + /// The object, containing a key/ value pair, to add to the shared access policies collection. + public void Add(KeyValuePair item) + { + this.Add(item.Key, item.Value); + } + + /// + /// Removes all keys and values from the shared access collection. + /// + public void Clear() + { + this.policies.Clear(); + } + + /// + /// Determines whether the collection of shared access policies contains the key and value in the specified object. + /// + /// A object containing the key and value to search for. + /// true if the shared access policies collection contains the specified key/value; otherwise, false. + public bool Contains(KeyValuePair item) + { + SharedAccessFilePolicy storedItem; + if (this.TryGetValue(item.Key, out storedItem)) + { + if (string.Equals( + SharedAccessFilePolicy.PermissionsToString(item.Value.Permissions), + SharedAccessFilePolicy.PermissionsToString(storedItem.Permissions), + StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } + + /// + /// Copies each key in the key/ value pair to a compatible one-dimensional array, starting at the specified index + /// of the target array. + /// + /// A one-dimensional array of objects that serves as the destination for the elements copied from the shared access policies collection. + /// The zero-based index in at which copying begins. + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + CommonUtility.AssertNotNull("array", array); + + foreach (KeyValuePair item in this.policies) + { + array[arrayIndex++] = item; + } + } + + /// + /// Gets the number of key/ value pairs contained in the shared access policies collection. + /// + /// The number of key/ value pairs contained in the shared access policies collection. + public int Count + { + get + { + return this.policies.Count; + } + } + + /// + /// Gets a value indicating whether the collection of shared access policies is read-only. + /// + /// true if the collection of shared access policies is read-only; otherwise, false. + public bool IsReadOnly + { + get + { + return false; + } + } + + /// + /// Removes the value, specified in the object, from the shared access policies collection. + /// + /// The object, containing a key and value, to remove from the shared access policies collection. + /// true if the item was successfully removed; otherwise, false. + public bool Remove(KeyValuePair item) + { + if (this.Contains(item)) + { + return this.Remove(item.Key); + } + else + { + return false; + } + } + + /// + /// Returns an enumerator that iterates through the collection of shared access policies. + /// + /// An of type . + public IEnumerator> GetEnumerator() + { + return this.policies.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the collection of shared access policies. + /// + /// An object that can be used to iterate through the collection of shared access policies. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + System.Collections.IEnumerable enumerable = this.policies; + return enumerable.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Lib/Common/File/SharedAccessFilePolicy.cs b/Lib/Common/File/SharedAccessFilePolicy.cs new file mode 100644 index 000000000..9da5aabf2 --- /dev/null +++ b/Lib/Common/File/SharedAccessFilePolicy.cs @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//----------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File +{ + using Microsoft.WindowsAzure.Storage.Core.Util; + using System; + using System.Text; + + /// + /// Represents a shared access policy, which specifies the start time, expiry time, + /// and permissions for a shared access signature. + /// + public sealed class SharedAccessFilePolicy + { + /// + /// Initializes a new instance of the class. + /// + public SharedAccessFilePolicy() + { + } + + /// + /// Gets or sets the start time for a shared access signature associated with this shared access policy. + /// + /// A specifying the shared access start time. + public DateTimeOffset? SharedAccessStartTime { get; set; } + + /// + /// Gets or sets the expiry time for a shared access signature associated with this shared access policy. + /// + /// A specifying the shared access expiry time. + public DateTimeOffset? SharedAccessExpiryTime { get; set; } + + /// + /// Gets or sets the permissions for a shared access signature associated with this shared access policy. + /// + /// A object. + public SharedAccessFilePermissions Permissions { get; set; } + + /// + /// Converts the permissions specified for the shared access policy to a string. + /// + /// A object. + /// The shared access permissions, in string format. + public static string PermissionsToString(SharedAccessFilePermissions permissions) + { + // The service supports a fixed order => rwdl + StringBuilder builder = new StringBuilder(); + + if ((permissions & SharedAccessFilePermissions.Read) == SharedAccessFilePermissions.Read) + { + builder.Append("r"); + } + + if ((permissions & SharedAccessFilePermissions.Write) == SharedAccessFilePermissions.Write) + { + builder.Append("w"); + } + + if ((permissions & SharedAccessFilePermissions.Delete) == SharedAccessFilePermissions.Delete) + { + builder.Append("d"); + } + + if ((permissions & SharedAccessFilePermissions.List) == SharedAccessFilePermissions.List) + { + builder.Append("l"); + } + + return builder.ToString(); + } + + /// + /// Constructs a object from a permissions string. + /// + /// The shared access permissions, in string format. + /// A object. + public static SharedAccessFilePermissions PermissionsFromString(string input) + { + CommonUtility.AssertNotNull("input", input); + + SharedAccessFilePermissions permissions = 0; + + foreach (char c in input) + { + switch (c) + { + case 'r': + permissions |= SharedAccessFilePermissions.Read; + break; + + case 'w': + permissions |= SharedAccessFilePermissions.Write; + break; + + case 'd': + permissions |= SharedAccessFilePermissions.Delete; + break; + + case 'l': + permissions |= SharedAccessFilePermissions.List; + break; + + default: + throw new ArgumentOutOfRangeException("input"); + } + } + + // Incase we ever change none to be something other than 0 + if (permissions == 0) + { + permissions |= SharedAccessFilePermissions.None; + } + + return permissions; + } + } +} diff --git a/Lib/Common/IRequestOptions.cs b/Lib/Common/IRequestOptions.cs index 7c12dc540..566c177e9 100644 --- a/Lib/Common/IRequestOptions.cs +++ b/Lib/Common/IRequestOptions.cs @@ -49,5 +49,13 @@ public interface IRequestOptions /// /// A containing the maximum execution time across all potential retries. TimeSpan? MaximumExecutionTime { get; set; } + +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + /// + /// Gets or sets a value to indicate whether data written and read by the client library should be encrypted. + /// + /// Use true to specify that data should be encrypted/decrypted for all transactions; otherwise, false. + bool? RequireEncryption { get; set; } +#endif } } diff --git a/Lib/Common/Queue/CloudQueue.Common.cs b/Lib/Common/Queue/CloudQueue.Common.cs index 98cde7a74..6c3cdaf66 100644 --- a/Lib/Common/Queue/CloudQueue.Common.cs +++ b/Lib/Common/Queue/CloudQueue.Common.cs @@ -186,7 +186,7 @@ private void ParseQueryAndVerify(StorageUri address, StorageCredentials credenti StorageCredentials parsedCredentials; this.StorageUri = NavigationHelper.ParseQueueTableQueryAndVerify(address, out parsedCredentials); - if ((parsedCredentials != null) && (credentials != null) && !parsedCredentials.Equals(credentials)) + if (parsedCredentials != null && credentials != null) { string error = string.Format(CultureInfo.CurrentCulture, SR.MultipleCredentialsProvided); throw new ArgumentException(error); @@ -199,14 +199,21 @@ private void ParseQueryAndVerify(StorageUri address, StorageCredentials credenti /// /// Returns the canonical name for shared access. /// + /// A string indicating the desired SAS version to use, in storage service version format. /// The canonical name. - private string GetCanonicalName() + private string GetCanonicalName(string sasVersion) { string accountName = this.ServiceClient.Credentials.AccountName; string queueName = this.Name; + string canonicalNameFormat = "/{0}/{1}/{2}"; + if (sasVersion == Constants.VersionConstants.February2012 || sasVersion == Constants.VersionConstants.August2013) + { + // Do not prepend service name for older versions + canonicalNameFormat = "/{1}/{2}"; + } - return string.Format(CultureInfo.InvariantCulture, "/{0}/{1}", accountName, queueName); - } + return string.Format(CultureInfo.InvariantCulture, canonicalNameFormat, SR.Queue, accountName, queueName); + } /// /// Selects the get message response. @@ -241,7 +248,7 @@ private CloudQueueMessage SelectPeekMessageResponse(QueueMessage protocolMessage if (options != null && options.EncryptionPolicy != null) { // If EncryptionPolicy is set, decrypt the message and set it. - dest = options.EncryptionPolicy.DecryptMessage(protocolMessage.Text); + dest = options.EncryptionPolicy.DecryptMessage(protocolMessage.Text, options.RequireEncryption); } #endif @@ -289,7 +296,7 @@ private CloudQueueMessage SelectPeekMessageResponse(QueueMessage protocolMessage /// The query string returned includes the leading question mark. public string GetSharedAccessSignature(SharedAccessQueuePolicy policy) { - return this.GetSharedAccessSignature(policy, null /* accessPolicyIdentifier */, null /* sasVersion */); + return this.GetSharedAccessSignature(policy, null /* accessPolicyIdentifier */); } /// @@ -301,7 +308,32 @@ public string GetSharedAccessSignature(SharedAccessQueuePolicy policy) /// The query string returned includes the leading question mark. public string GetSharedAccessSignature(SharedAccessQueuePolicy policy, string accessPolicyIdentifier) { - return this.GetSharedAccessSignature(policy, accessPolicyIdentifier, null /* sasVersion */); + if (!this.ServiceClient.Credentials.IsSharedKey) + { + string errorMessage = string.Format(CultureInfo.CurrentCulture, SR.CannotCreateSASWithoutAccountKey); + throw new InvalidOperationException(errorMessage); + } + + string resourceName = this.GetCanonicalName(Constants.HeaderConstants.TargetStorageVersion); + StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; + + string signature = SharedAccessSignatureHelper.GetHash( + policy, + accessPolicyIdentifier, + resourceName, + Constants.HeaderConstants.TargetStorageVersion, + accountKey.KeyValue); + + string accountKeyName = accountKey.KeyName; + + UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature( + policy, + accessPolicyIdentifier, + signature, + accountKeyName, + Constants.HeaderConstants.TargetStorageVersion); + + return builder.ToString(); } /// @@ -312,6 +344,7 @@ public string GetSharedAccessSignature(SharedAccessQueuePolicy policy, string ac /// A string indicating the desired SAS version to use, in storage service version format. Value must be 2012-02-12 or 2013-08-15. /// A shared access signature, as a URI query string. /// The query string returned includes the leading question mark. + [Obsolete("This overload has been deprecated because the SAS tokens generated using the current version work fine with old libraries. Please use the other overloads.")] public string GetSharedAccessSignature(SharedAccessQueuePolicy policy, string accessPolicyIdentifier, string sasVersion) { if (!this.ServiceClient.Credentials.IsSharedKey) @@ -321,7 +354,7 @@ public string GetSharedAccessSignature(SharedAccessQueuePolicy policy, string ac } string validatedSASVersion = SharedAccessSignatureHelper.ValidateSASVersionString(sasVersion); - string resourceName = this.GetCanonicalName(); + string resourceName = this.GetCanonicalName(validatedSASVersion); StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; string signature = SharedAccessSignatureHelper.GetHash( diff --git a/Lib/Common/Queue/CloudQueueMessage.Common.cs b/Lib/Common/Queue/CloudQueueMessage.Common.cs index 194e36755..59e152dcf 100644 --- a/Lib/Common/Queue/CloudQueueMessage.Common.cs +++ b/Lib/Common/Queue/CloudQueueMessage.Common.cs @@ -249,8 +249,12 @@ internal string GetMessageContentForTransfer(bool shouldEncodeMessage, QueueRequ string outgoingMessageString = null; #if !(WINDOWS_RT || ASPNET_K || PORTABLE) - if (options != null && options.EncryptionPolicy != null) + if (options != null) { + options.AssertPolicyIfRequired(); + + if (options.EncryptionPolicy != null) + { // Create an encrypted message that will hold the message contents along with encryption related metadata and return it. // The encrypted message is already Base 64 encoded. So no need to process further in this method. string encryptedMessageString = options.EncryptionPolicy.EncryptMessage(this.AsBytes); @@ -265,6 +269,7 @@ internal string GetMessageContentForTransfer(bool shouldEncodeMessage, QueueRequ } return encryptedMessageString; + } } #endif diff --git a/Lib/Common/Queue/QueueRequestOptions.cs b/Lib/Common/Queue/QueueRequestOptions.cs index 5909ba0e0..d07cc8813 100644 --- a/Lib/Common/Queue/QueueRequestOptions.cs +++ b/Lib/Common/Queue/QueueRequestOptions.cs @@ -53,6 +53,7 @@ internal QueueRequestOptions(QueueRequestOptions other) this.RetryPolicy = other.RetryPolicy; #if !(WINDOWS_RT || ASPNET_K || PORTABLE) this.EncryptionPolicy = other.EncryptionPolicy; + this.RequireEncryption = other.RequireEncryption; #endif this.ServerTimeout = other.ServerTimeout; this.LocationMode = other.LocationMode; @@ -68,6 +69,7 @@ internal static QueueRequestOptions ApplyDefaults(QueueRequestOptions options, C modifiedOptions.RetryPolicy = modifiedOptions.RetryPolicy ?? serviceClient.DefaultRequestOptions.RetryPolicy; #if !(WINDOWS_RT || ASPNET_K || PORTABLE) modifiedOptions.EncryptionPolicy = modifiedOptions.EncryptionPolicy ?? serviceClient.DefaultRequestOptions.EncryptionPolicy; + modifiedOptions.RequireEncryption = modifiedOptions.RequireEncryption ?? serviceClient.DefaultRequestOptions.RequireEncryption; #endif modifiedOptions.LocationMode = (modifiedOptions.LocationMode ?? serviceClient.DefaultRequestOptions.LocationMode) @@ -105,6 +107,16 @@ internal void ApplyToStorageCommand(RESTCommand cmd) } } +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + internal void AssertPolicyIfRequired() + { + if (this.RequireEncryption.HasValue && this.RequireEncryption.Value && this.EncryptionPolicy == null) + { + throw new InvalidOperationException(SR.EncryptionPolicyMissingInStrictMode); + } + } +#endif + /// /// Gets or sets the absolute expiry time across all potential retries for the request. /// @@ -122,6 +134,12 @@ internal void ApplyToStorageCommand(RESTCommand cmd) /// /// An object of type . public QueueEncryptionPolicy EncryptionPolicy { get; set; } + + /// + /// Gets or sets a value to indicate whether data written and read by the client library should be encrypted. + /// + /// Use true to specify that data should be encrypted/decrypted for all transactions; otherwise, false. + public bool? RequireEncryption { get; set; } #endif /// diff --git a/Lib/Common/Queue/SharedAccessQueuePolicies.cs b/Lib/Common/Queue/SharedAccessQueuePolicies.cs index 4907f27ea..f2c3e684a 100644 --- a/Lib/Common/Queue/SharedAccessQueuePolicies.cs +++ b/Lib/Common/Queue/SharedAccessQueuePolicies.cs @@ -159,7 +159,7 @@ public bool Contains(KeyValuePair item) /// /// Copies each key/ value pair in the shared access policies collection to a compatible one-dimensional array, starting at the specified index of the target array. /// - /// The one-dimensional array of objects that is the destination of the elements copied from the shared access policies collection. + /// A one-dimensional array of objects that serves as the destination for the elements copied from the shared access policies collection. /// The zero-based index in at which copying begins. public void CopyTo(KeyValuePair[] array, int arrayIndex) { diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index 599016899..491e333fa 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -373,6 +373,11 @@ static class Constants /// public const string BlockBlobValue = "BlockBlob"; + /// + /// Constant signaling an append blob. + /// + public const string AppendBlobValue = "AppendBlob"; + /// /// Constant signaling the blob is locked. /// @@ -498,6 +503,11 @@ static class Constants /// public const string ShareElement = "Share"; + /// + /// XML element for Share Quota. + /// + public const string QuotaElement = "Quota"; + /// /// XML element for file ranges. /// @@ -739,7 +749,7 @@ static HeaderConstants() #elif WINDOWS_RT UserAgentComment = "(Windows Runtime)"; #elif ASPNET_K -#if ASPNETCORE50 +#if DNXCORE50 UserAgentComment = "(ASP.NET Core 5.0)"; #else UserAgentComment = "(ASP.NET 5.0)"; @@ -771,7 +781,11 @@ static HeaderConstants() /// /// Specifies the value to use for UserAgent header. /// - public const string UserAgentProductVersion = "4.4.1-preview"; +#if ASPNET_K || PORTABLE + public const string UserAgentProductVersion = "5.0.1-preview"; +#else + public const string UserAgentProductVersion = "5.0.0"; +#endif /// /// Master Windows Azure Storage header prefix. @@ -963,6 +977,16 @@ static HeaderConstants() /// public const string SequenceNumberAction = PrefixForStorageHeader + "sequence-number-action"; + /// + /// Header that specifies committed block count. + /// + public const string BlobCommittedBlockCount = PrefixForStorageHeader + "blob-committed-block-count"; + + /// + /// Header that specifies the blob append offset. + /// + public const string BlobAppendOffset = PrefixForStorageHeader + "blob-append-offset"; + /// /// Header for the If-Sequence-Number-LE condition. /// @@ -978,6 +1002,16 @@ static HeaderConstants() /// public const string IfSequenceNumberEqHeader = PrefixForStorageHeader + "if-sequence-number-eq"; + /// + /// Header for the blob-condition-maxsize condition. + /// + public const string IfMaxSizeLessThanOrEqualHeader = PrefixForStorageHeader + "blob-condition-maxsize"; + + /// + /// Header for the blob-condition-appendpos condition. + /// + public const string IfAppendPositionEqualHeader = PrefixForStorageHeader + "blob-condition-appendpos"; + /// /// Header that specifies lease ID. /// @@ -1032,7 +1066,7 @@ static HeaderConstants() /// Current storage version header value. /// Every time this version changes, assembly version needs to be updated as well. /// - public const string TargetStorageVersion = "2014-02-14"; + public const string TargetStorageVersion = "2015-02-21"; /// /// Specifies the file type. @@ -1049,6 +1083,11 @@ static HeaderConstants() /// public const string BlockBlob = "BlockBlob"; + /// + /// Specifies the append blob type. + /// + public const string AppendBlob = "AppendBlob"; + /// /// Specifies only snapshots are to be included. /// @@ -1145,6 +1184,16 @@ static HeaderConstants() /// public const string CopyActionAbort = "abort"; + /// + /// Header that specifies the share size, in gigabytes. + /// + public const string ShareSize = PrefixForStorageHeader + "share-size"; + + /// + /// Header that specifies the share quota, in gigabytes. + /// + public const string ShareQuota = PrefixForStorageHeader + "share-quota"; + /// /// Header that specifies the Accept type for the response payload. /// diff --git a/Lib/Common/Shared/Protocol/Request.cs b/Lib/Common/Shared/Protocol/Request.cs index 7194d503f..56da9228e 100644 --- a/Lib/Common/Shared/Protocol/Request.cs +++ b/Lib/Common/Shared/Protocol/Request.cs @@ -28,6 +28,16 @@ namespace Microsoft.WindowsAzure.Storage.Shared.Protocol internal static class Request { + /// + /// Converts the date time to snapshot string. + /// + /// The date time. + /// The converted string. + internal static string ConvertDateTimeToSnapshotString(DateTimeOffset dateTime) + { + return dateTime.UtcDateTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffff'Z'", CultureInfo.InvariantCulture); + } + /// /// Writes a collection of shared access policies to the specified stream in XML format. /// diff --git a/Lib/Common/Shared/Protocol/ServiceProperties.cs b/Lib/Common/Shared/Protocol/ServiceProperties.cs index 1a2e627c7..6fafa4055 100644 --- a/Lib/Common/Shared/Protocol/ServiceProperties.cs +++ b/Lib/Common/Shared/Protocol/ServiceProperties.cs @@ -449,7 +449,7 @@ private static MetricsProperties ReadMetricsPropertiesFromXml(XElement element) /// /// The XML element. /// A CorsProperties object containing the properties in the element. - private static CorsProperties ReadCorsPropertiesFromXml(XElement element) + internal static CorsProperties ReadCorsPropertiesFromXml(XElement element) { CorsProperties ret = new CorsProperties(); diff --git a/Lib/Common/Table/CloudTable.Common.cs b/Lib/Common/Table/CloudTable.Common.cs index 4ab286d2e..1d277d83f 100644 --- a/Lib/Common/Table/CloudTable.Common.cs +++ b/Lib/Common/Table/CloudTable.Common.cs @@ -21,6 +21,7 @@ namespace Microsoft.WindowsAzure.Storage.Table using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Auth; using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -129,8 +130,7 @@ public string GetSharedAccessSignature(SharedAccessTablePolicy policy) null /* startPartitionKey */, null /* startRowKey */, null /* endPartitionKey */, - null /* endRowKey */, - null /* sasVersion */); + null /* endRowKey */); } /// @@ -149,8 +149,7 @@ public string GetSharedAccessSignature(SharedAccessTablePolicy policy, string ac null /* startPartitionKey */, null /* startRowKey */, null /* endPartitionKey */, - null /* endRowKey */, - null /* sasVersion */); + null /* endRowKey */); } /// @@ -173,14 +172,39 @@ public string GetSharedAccessSignature( string endPartitionKey, string endRowKey) { - return this.GetSharedAccessSignature( + if (!this.ServiceClient.Credentials.IsSharedKey) + { + string errorMessage = string.Format(CultureInfo.CurrentCulture, SR.CannotCreateSASWithoutAccountKey); + throw new InvalidOperationException(errorMessage); + } + + string resourceName = this.GetCanonicalName(Constants.HeaderConstants.TargetStorageVersion); + StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; + + string signature = SharedAccessSignatureHelper.GetHash( policy, accessPolicyIdentifier, startPartitionKey, startRowKey, endPartitionKey, endRowKey, - null /* sasVersion */); + resourceName, + Constants.HeaderConstants.TargetStorageVersion, + accountKey.KeyValue); + + UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature( + policy, + this.Name, + accessPolicyIdentifier, + startPartitionKey, + startRowKey, + endPartitionKey, + endRowKey, + signature, + accountKey.KeyName, + Constants.HeaderConstants.TargetStorageVersion); + + return builder.ToString(); } /// @@ -196,6 +220,7 @@ public string GetSharedAccessSignature( /// A shared access signature, as a URI query string. /// The query string returned includes the leading question mark. /// Thrown if the current credentials don't support creating a shared access signature. + [Obsolete("This overload has been deprecated because the SAS tokens generated using the current version work fine with old libraries. Please use the other overloads.")] public string GetSharedAccessSignature( SharedAccessTablePolicy policy, string accessPolicyIdentifier, @@ -212,7 +237,7 @@ public string GetSharedAccessSignature( } string validatedSASVersion = SharedAccessSignatureHelper.ValidateSASVersionString(sasVersion); - string resourceName = this.GetCanonicalName(); + string resourceName = this.GetCanonicalName(validatedSASVersion); StorageAccountKey accountKey = this.ServiceClient.Credentials.Key; string signature = SharedAccessSignatureHelper.GetHash( @@ -261,7 +286,7 @@ private void ParseQueryAndVerify(StorageUri address, StorageCredentials credenti StorageCredentials parsedCredentials; this.StorageUri = NavigationHelper.ParseQueueTableQueryAndVerify(address, out parsedCredentials); - if ((parsedCredentials != null) && (credentials != null) && !parsedCredentials.Equals(credentials)) + if (parsedCredentials != null && credentials != null) { string error = string.Format(CultureInfo.CurrentCulture, SR.MultipleCredentialsProvided); throw new ArgumentException(error); @@ -274,14 +299,21 @@ private void ParseQueryAndVerify(StorageUri address, StorageCredentials credenti /// /// Gets the canonical name of the table, formatted as table/<account-name>/<table-name>. /// + /// A string indicating the desired SAS version to use, in storage service version format. /// The canonical name of the table. [SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.String.ToLower", Justification = "ToLower(CultureInfo) is not present in RT and ToLowerInvariant() also violates FxCop")] - private string GetCanonicalName() + private string GetCanonicalName(string sasVersion) { string accountName = this.ServiceClient.Credentials.AccountName; string tableNameLowerCase = this.Name.ToLower(); + string canonicalNameFormat = "/{0}/{1}/{2}"; + if (sasVersion == Constants.VersionConstants.February2012 || sasVersion == Constants.VersionConstants.August2013) + { + // Do not prepend service name for older versions + canonicalNameFormat = "/{1}/{2}"; + } - return string.Format(CultureInfo.InvariantCulture, "/{0}/{1}", accountName, tableNameLowerCase); + return string.Format(CultureInfo.InvariantCulture, canonicalNameFormat, SR.Table, accountName, tableNameLowerCase); } } } diff --git a/Lib/Common/Table/SharedAccessTablePolicies.cs b/Lib/Common/Table/SharedAccessTablePolicies.cs index f736ccff8..676ee0079 100644 --- a/Lib/Common/Table/SharedAccessTablePolicies.cs +++ b/Lib/Common/Table/SharedAccessTablePolicies.cs @@ -147,7 +147,7 @@ public bool Contains(KeyValuePair item) /// /// Copies each key/ value pair in the shared access policies collection to a compatible one-dimensional array, starting at the specified index of the target array. /// - /// The one-dimensional array of objects that is the destination of the elements copied from the shared access policies collection. + /// A one-dimensional array of objects that serves as the destination for the elements copied from the shared access policies collection. /// The zero-based index in at which copying begins. public void CopyTo(KeyValuePair[] array, int arrayIndex) { diff --git a/Lib/Common/Table/TableBatchOperation.Common.cs b/Lib/Common/Table/TableBatchOperation.Common.cs index b1788b2bf..86b5cb46f 100644 --- a/Lib/Common/Table/TableBatchOperation.Common.cs +++ b/Lib/Common/Table/TableBatchOperation.Common.cs @@ -262,7 +262,7 @@ public bool Contains(TableOperation item) /// /// Copies all the elements of the to the specified one-dimensional array starting at the specified destination array index. /// - /// The one-dimensional array that is the destination of the elements copied from the . + /// A one-dimensional array that serves as the destination for the elements copied from the . /// The index in the destination array at which copying begins. public void CopyTo(TableOperation[] array, int arrayIndex) { diff --git a/Lib/Common/Table/TableEntity.cs b/Lib/Common/Table/TableEntity.cs index abec8eca3..a784c088a 100644 --- a/Lib/Common/Table/TableEntity.cs +++ b/Lib/Common/Table/TableEntity.cs @@ -312,6 +312,7 @@ private static IDictionary ReflectionWrite(object entity } EntityProperty newProperty = EntityProperty.CreateEntityPropertyFromObject(property.GetValue(entity, null), property.PropertyType); + // Add the fact that this property needs to be encrypted // properties with [EncryptAttribute] #if !(WINDOWS_RT || ASPNET_K || PORTABLE) diff --git a/Lib/Common/Table/TableRequestOptions.cs b/Lib/Common/Table/TableRequestOptions.cs index f38597115..34b317613 100644 --- a/Lib/Common/Table/TableRequestOptions.cs +++ b/Lib/Common/Table/TableRequestOptions.cs @@ -60,6 +60,7 @@ public TableRequestOptions(TableRequestOptions other) #if !(WINDOWS_RT || ASPNET_K || PORTABLE) this.PropertyResolver = other.PropertyResolver; this.EncryptionPolicy = other.EncryptionPolicy; + this.RequireEncryption = other.RequireEncryption; this.EncryptionResolver = other.EncryptionResolver; #endif } @@ -93,6 +94,7 @@ internal static TableRequestOptions ApplyDefaults(TableRequestOptions requestOpt #if !(WINDOWS_RT || ASPNET_K || PORTABLE) modifiedOptions.PropertyResolver = modifiedOptions.PropertyResolver ?? serviceClient.DefaultRequestOptions.PropertyResolver; modifiedOptions.EncryptionPolicy = modifiedOptions.EncryptionPolicy ?? serviceClient.DefaultRequestOptions.EncryptionPolicy; + modifiedOptions.RequireEncryption = modifiedOptions.RequireEncryption ?? serviceClient.DefaultRequestOptions.RequireEncryption; modifiedOptions.EncryptionResolver = modifiedOptions.EncryptionResolver ?? serviceClient.DefaultRequestOptions.EncryptionResolver; #endif @@ -140,6 +142,26 @@ private void ApplyToStorageCommandCommon(StorageCommandBase cmd) } } +#if !(WINDOWS_RT || ASPNET_K || PORTABLE) + internal void AssertNoEncryptionPolicyOrStrictMode() + { + if (this.EncryptionPolicy != null) + { + throw new InvalidOperationException(SR.EncryptionNotSupportedForOperation); + } + + this.AssertPolicyIfRequired(); + } + + internal void AssertPolicyIfRequired() + { + if (this.RequireEncryption.HasValue && this.RequireEncryption.Value && this.EncryptionPolicy == null) + { + throw new InvalidOperationException(SR.EncryptionPolicyMissingInStrictMode); + } + } +#endif + /// /// Gets or sets the absolute expiry time across all potential retries for the request. /// @@ -157,6 +179,12 @@ private void ApplyToStorageCommandCommon(StorageCommandBase cmd) /// /// An object of type . public TableEncryptionPolicy EncryptionPolicy { get; set; } + + /// + /// Gets or sets a value to indicate whether data written and read by the client library should be encrypted. + /// + /// Use true to specify that data should be encrypted/decrypted for all transactions; otherwise, false. + public bool? RequireEncryption { get; set; } #endif /// @@ -236,8 +264,8 @@ public Func PropertyResolver private Func encryptionResolver = null; /// - /// Gets or sets the delegate that is used to get the the value indicating whether a property should be encrypted or not given the partition key, row key, - /// and the property name. + /// Gets or sets the delegate to get the value indicating whether or not a property should be encrypted, given the partition key, row key, + /// and property name. /// public Func EncryptionResolver { diff --git a/Lib/Portable/Microsoft.WindowsAzure.Storage.csproj b/Lib/Portable/Microsoft.WindowsAzure.Storage.csproj index a2c27055b..e6b8e1645 100644 --- a/Lib/Portable/Microsoft.WindowsAzure.Storage.csproj +++ b/Lib/Portable/Microsoft.WindowsAzure.Storage.csproj @@ -97,6 +97,9 @@ Blob\BlockSearchMode.cs + + Blob\CloudAppendBlob.Common.cs + Blob\CloudBlob.Common.cs @@ -604,6 +607,9 @@ Blob\CloudBlob.cs + + Blob\CloudAppendBlob.cs + Blob\CloudBlobClient.cs diff --git a/Lib/Portable/Properties/AssemblyInfo.cs b/Lib/Portable/Properties/AssemblyInfo.cs index 196b3701c..bdab00ca4 100644 --- a/Lib/Portable/Properties/AssemblyInfo.cs +++ b/Lib/Portable/Properties/AssemblyInfo.cs @@ -26,6 +26,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.4.1.0")] -[assembly: AssemblyFileVersion("4.4.1.0")] -[assembly: AssemblyInformationalVersion("4.4.1.0-preview")] +[assembly: AssemblyVersion("5.0.1.0")] +[assembly: AssemblyFileVersion("5.0.1.0")] +[assembly: AssemblyInformationalVersion("5.0.1.0-preview")] diff --git a/Lib/WindowsDesktop/GlobalSuppressions.cs b/Lib/WindowsDesktop/GlobalSuppressions.cs index 0eeb76265..b2af84a08 100644 --- a/Lib/WindowsDesktop/GlobalSuppressions.cs +++ b/Lib/WindowsDesktop/GlobalSuppressions.cs @@ -55,6 +55,9 @@ [assembly: SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudPageBlob.#BeginStartCopy(Microsoft.WindowsAzure.Storage.Blob.CloudPageBlob,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.File.Protocol.DirectoryHttpWebRequestFactory.#List(System.Uri,System.Nullable`1,Microsoft.WindowsAzure.Storage.File.Protocol.FileListingContext,System.Nullable`1,System.Boolean,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.File.Protocol.ShareHttpResponseParsers.#GetAcl(System.Net.HttpWebResponse)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#StartCopy(Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#BeginStartCopy(Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#BeginStartCopy(Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] // CA1014 [assembly: SuppressMessage("Microsoft.Design", "CA1014:MarkAssembliesWithClsCompliant", Justification = "Reviewed")] @@ -290,14 +293,36 @@ [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#CreateSnapshot(System.Collections.Generic.IDictionary`2,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudPageBlob.#Create(System.Int64,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.CloudTable.#DeleteIfExists(Microsoft.WindowsAzure.Storage.Table.TableRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#AppendBlock(System.IO.Stream,System.String,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#Create(Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#AppendBlock(System.IO.Stream,System.String,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#DownloadText(System.Text.Encoding,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#CreateSnapshot(System.Collections.Generic.IDictionary`2,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#StartCopy(Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#CreateSnapshot(System.Collections.Generic.IDictionary`2,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudPageBlob.#Snapshot(System.Collections.Generic.IDictionary`2,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#Snapshot(System.Collections.Generic.IDictionary`2,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#Snapshot(System.Collections.Generic.IDictionary`2,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#Snapshot(System.Collections.Generic.IDictionary`2,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.File.CloudFile.#StartCopy(Microsoft.WindowsAzure.Storage.Blob.CloudBlob,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.File.FileRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlobClient.#GetBlobReferenceFromServer(Microsoft.WindowsAzure.Storage.StorageUri,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#AppendBlockAsync(System.IO.Stream,System.String)", Justification = "Backward compatibility")] [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlobClient.#GetBlobReferenceFromServer(System.Uri,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlobContainer.#GetBlobReferenceFromServer(System.String,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#OpenWrite(System.Nullable`1,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#UploadFromStream(System.IO.Stream,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#UploadFromStream(System.IO.Stream,System.Int64,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#UploadFromFile(System.String,System.IO.FileMode,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#UploadFromByteArray(System.Byte[],System.Int32,System.Int32,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#UploadText(System.String,System.Text.Encoding,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#OpenWrite(System.Boolean,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#AppendFromStream(System.IO.Stream,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#AppendFromStream(System.IO.Stream,System.Int64,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#AppendFromFile(System.String,System.IO.FileMode,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#AppendFromByteArray(System.Byte[],System.Int32,System.Int32,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#AppendText(System.String,System.Text.Encoding,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#CreateOrReplace(Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.File.CloudFileShare.#GetStats(Microsoft.WindowsAzure.Storage.File.FileRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] // CA1031 [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#BeginOpenWrite(Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Needed to ensure exceptions are not thrown on threadpool thread.")] @@ -385,7 +410,13 @@ [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#DeleteIfExistsHandler(Microsoft.WindowsAzure.Storage.Blob.DeleteSnapshotsOption,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,Microsoft.WindowsAzure.Storage.Core.Util.StorageAsyncResult`1)", Justification = "Needed to ensure exceptions are not thrown on threadpool thread.")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#BeginOpenRead(Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Needed to ensure exceptions are not thrown on threadpool thread.")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#BlobOutputStreamCommitCallback(System.IAsyncResult)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#DownloadTextCallback(System.IAsyncResult)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobWriteStream.#WriteAppendBlock(System.IO.Stream,System.String,System.Int64,Microsoft.WindowsAzure.Storage.Core.Util.StorageAsyncResult`1)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobWriteStream.#AppendBlockCallback(System.IAsyncResult)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#UploadFromFileCallback(System.IAsyncResult)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobWriteStream.#WriteAppendBlock(System.IO.Stream,System.Int64,System.String,Microsoft.WindowsAzure.Storage.Core.Util.StorageAsyncResult`1)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.TruncatingNonCloseableStream.#BeginRead(System.Byte[],System.Int32,System.Int32,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.BlobDecryptStream.#WriteStreamCallback(System.IAsyncResult)", Justification = "Reviewed.")] // CA1040 [assembly: SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Scope = "type", Target = "Microsoft.WindowsAzure.Storage.IContinuationToken", Justification = "Reviewed : Specifies a common base type a continuation token can be cast to")] @@ -399,11 +430,13 @@ // CA1062 [assembly: SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.DataServices.TableServiceContext.#.ctor(Microsoft.WindowsAzure.Storage.Table.CloudTableClient)", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.StorageException.#TranslateException(System.Exception,Microsoft.WindowsAzure.Storage.RequestResult,System.Func`2)", Justification = "Used as inner exception internally. Reviewed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.SyncMemoryStream.#.ctor(System.Byte[],System.Int32)", Justification = "Reviewed")] // CA1304 [assembly: SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.String.ToLower", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.Protocol.BlobHttpWebRequestFactory.#AddLeaseAction(System.Net.HttpWebRequest,Microsoft.WindowsAzure.Storage.Blob.LeaseAction)", Justification = "ToLower(CultureInfo) is not present in RT and ToLowerInvariant() also violates FxCop.")] [assembly: SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.String.ToLower", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.Protocol.ContainerHttpWebRequestFactory.#SetAcl(System.Uri,System.Nullable`1,Microsoft.WindowsAzure.Storage.Blob.BlobContainerPublicAccessType,Microsoft.WindowsAzure.Storage.AccessCondition,System.Boolean,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "ToLower(CultureInfo) is not present in RT and ToLowerInvariant() also violates FxCop.")] [assembly: SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.String.ToLower", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Auth.SharedAccessSignatureHelper.#ParseQuery(System.Collections.Generic.IDictionary`2,System.Boolean)", Justification = "ToLower(CultureInfo) is not present in RT and ToLowerInvariant() also violates FxCop.")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.String.ToLower", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Auth.SharedAccessSignatureHelper.#ParseQuery(System.Collections.Generic.IDictionary`2)", Justification = "ToLower(CultureInfo) is not present in RT and ToLowerInvariant() also violates FxCop.")] // CA1307 [assembly: SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Protocol.TableRequest.#ExtractEntityIndexFromExtendedErrorInformation(Microsoft.WindowsAzure.Storage.RequestResult)", MessageId = "System.String.IndexOf(System.String)", Justification = "Compatibility with RT")] @@ -460,6 +493,8 @@ [assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#GetBlobImpl(Microsoft.WindowsAzure.Storage.Blob.BlobAttributes,System.IO.Stream,System.Nullable`1,System.Nullable`1,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions)", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "type", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlobClient.#GetBlobReferenceFromServerImpl(Microsoft.WindowsAzure.Storage.StorageUri,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "type", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "type", Target = "Microsoft.WindowsAzure.Storage.Table.CloudTable", Justification = "Reviewed")] // CA1702 [assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "etag", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.AccessCondition.#GenerateIfNoneMatchCondition(System.String)", Justification = "Reviewed")] @@ -504,6 +539,7 @@ // CA1801 [assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "columns", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableQuery.#Project`1(!!0,System.String[])", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "options", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Queryable.ExpressionParser.#VisitCustomQueryOptions(System.Collections.Generic.Dictionary`2)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "publicAccess", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.File.Protocol.ShareHttpWebRequestFactory.#SetAcl(System.Uri,System.Nullable`1,Microsoft.WindowsAzure.Storage.File.FileSharePublicAccessType,Microsoft.WindowsAzure.Storage.AccessCondition,System.Boolean,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed")] // CA1810 [assembly: SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Logger.#.cctor()", Justification = "Reviewed - Used to initilialize static data before any static members are referenced")] @@ -621,6 +657,17 @@ [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#BeginOpenRead(Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#BeginDownloadToFile(System.String,System.IO.FileMode,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#BeginDownloadRangeToByteArray(System.Byte[],System.Int32,System.Nullable`1,System.Nullable`1,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#BeginAppendBlock(System.IO.Stream,System.String,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#AppendBlock(System.IO.Stream,System.String,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#AppendBlock(System.IO.Stream,System.String,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#BeginAppendBlock(System.IO.Stream,System.String,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#BeginDownloadText(System.Text.Encoding,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#BeginOpenWrite(System.Nullable`1,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#BeginUploadFromFile(System.String,System.IO.FileMode,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#BeginUploadFromByteArray(System.Byte[],System.Int32,System.Int32,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#BeginOpenWrite(System.Boolean,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#BeginAppendFromFile(System.String,System.IO.FileMode,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#BeginAppendFromByteArray(System.Byte[],System.Int32,System.Int32,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.TruncatingNonCloseableStream.#BeginRead(System.Byte[],System.Int32,System.Int32,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] // CA2204 @@ -633,6 +680,18 @@ [assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "KeyWrapper", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptionPolicy.#EncryptBlob(System.IO.Stream,System.Collections.Generic.IDictionary`2)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "KeyWrapper", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptionPolicy.#DecryptBlob(System.IO.Stream,System.Collections.Generic.IDictionary`2)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "KeyResolver", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptionPolicy.#DecryptBlob(System.IO.Stream,System.Collections.Generic.IDictionary`2)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "UploadFromStream", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptedWriteStream.#.ctor(Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.Security.Cryptography.ICryptoTransform)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "FullBlob", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptedWriteStream.#.ctor(Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.Security.Cryptography.ICryptoTransform)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "BlobEncryptionMode", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptedWriteStream.#.ctor(Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.Security.Cryptography.ICryptoTransform)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "FullBlob", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptedWriteStream.#.ctor(Microsoft.WindowsAzure.Storage.Blob.CloudPageBlob,System.Int64,System.Boolean,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.Security.Cryptography.ICryptoTransform)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "BlobEncryptionMode", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptedWriteStream.#.ctor(Microsoft.WindowsAzure.Storage.Blob.CloudPageBlob,System.Int64,System.Boolean,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.Security.Cryptography.ICryptoTransform)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequireEncryption", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableRequestOptions.#AssertPolicyIfRequired()", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "EncryptionPolicy", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableRequestOptions.#AssertNoEncryptionPolicyOrStrictMode()", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequestOptions", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableRequestOptions.#AssertNoEncryptionPolicyOrStrictMode()", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequireEncryption", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Protocol.TableOperationHttpResponseParsers.#ReadAndResolveWithEdmTypeResolver`1(System.Collections.Generic.Dictionary`2,System.Func`6,System.String,!!0>,System.Func`5,System.String,System.Type,Microsoft.WindowsAzure.Storage.OperationContext,System.Boolean,Microsoft.WindowsAzure.Storage.Table.TableRequestOptions)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequireEncryption", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Protocol.TableOperationHttpResponseParsers.#ReadAndResolve`1(Microsoft.Data.OData.ODataEntry,System.Func`6,System.String,!!0>,Microsoft.WindowsAzure.Storage.Table.TableRequestOptions)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequireEncryption", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Queue.QueueRequestOptions.#AssertPolicyIfRequired()", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequireEncryption", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Queue.QueueEncryptionPolicy.#DecryptMessage(System.String,System.Nullable`1)", Justification = "Reviewed.")] // CA2208 [assembly: SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Util.AsyncStreamCopier`1.#EndOperation(System.IAsyncResult)", Justification = "Reviewed")] diff --git a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs index 7c6f29f1b..e7baa0bea 100644 --- a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs @@ -34,9 +34,8 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.4.1.0")] -[assembly: AssemblyFileVersion("4.4.1.0")] -[assembly: AssemblyInformationalVersion("4.4.1.0-preview")] +[assembly: AssemblyVersion("5.0.0.0")] +[assembly: AssemblyFileVersion("5.0.0.0")] #if SIGN [assembly: InternalsVisibleTo( diff --git a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec index ed540102a..863d83b54 100644 --- a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec +++ b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 4.3.0 + 5.0.0 Windows Azure Storage Microsoft Microsoft @@ -17,20 +17,19 @@ Microsoft Azure Storage team's blog - http://blogs.msdn.com/b/windowsazurestorag Microsoft, Azure, Storage, Table, Blob, File, Queue, Scalable, windowsazureofficial - - - - + + + - + - + - - + + diff --git a/Lib/WindowsDesktop/packages.config b/Lib/WindowsDesktop/packages.config index 4f386752d..f32cf6df5 100644 --- a/Lib/WindowsDesktop/packages.config +++ b/Lib/WindowsDesktop/packages.config @@ -1,9 +1,9 @@  - - - - + + + + - + \ No newline at end of file diff --git a/Lib/WindowsPhone/Properties/AssemblyInfo.cs b/Lib/WindowsPhone/Properties/AssemblyInfo.cs index 4bfacbf92..13b946c9e 100644 --- a/Lib/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhone/Properties/AssemblyInfo.cs @@ -32,9 +32,8 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("4.4.1.0")] -[assembly: AssemblyFileVersion("4.4.1.0")] -[assembly: AssemblyInformationalVersion("4.4.1.0-preview")] +[assembly: AssemblyVersion("5.0.0.0")] +[assembly: AssemblyFileVersion("5.0.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] #if SIGN diff --git a/Lib/WindowsPhone/packages.config b/Lib/WindowsPhone/packages.config index bd0463b02..b1d6e55dd 100644 --- a/Lib/WindowsPhone/packages.config +++ b/Lib/WindowsPhone/packages.config @@ -1,8 +1,8 @@  - - - + + + - + \ No newline at end of file diff --git a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs index a93b64a53..64229b63a 100644 --- a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs @@ -24,9 +24,8 @@ // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("4.4.1.0")] -[assembly: AssemblyFileVersion("4.4.1.0")] -[assembly: AssemblyInformationalVersion("4.4.1.0-preview")] +[assembly: AssemblyVersion("5.0.0.0")] +[assembly: AssemblyFileVersion("5.0.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] [assembly: ComVisible(false)] diff --git a/Lib/WindowsPhoneRT/packages.config b/Lib/WindowsPhoneRT/packages.config index 088754db7..47ccc6ac6 100644 --- a/Lib/WindowsPhoneRT/packages.config +++ b/Lib/WindowsPhoneRT/packages.config @@ -1,6 +1,6 @@  - - - + + + \ No newline at end of file diff --git a/Lib/WindowsRuntime/Blob/BlobWriteStream.cs b/Lib/WindowsRuntime/Blob/BlobWriteStream.cs index 26ebc16d1..f43dfec07 100644 --- a/Lib/WindowsRuntime/Blob/BlobWriteStream.cs +++ b/Lib/WindowsRuntime/Blob/BlobWriteStream.cs @@ -55,6 +55,18 @@ internal BlobWriteStream(CloudPageBlob pageBlob, long pageBlobSize, bool createN { } + /// + /// Initializes a new instance of the BlobWriteStream class for an append blob. + /// + /// Blob reference to write to. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// An object that specifies additional options for the request. + /// An object that represents the context for the current operation. + internal BlobWriteStream(CloudAppendBlob appendBlob, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + : base(appendBlob, accessCondition, options, operationContext) + { + } + /// /// Sets the position within the current stream. /// @@ -79,7 +91,7 @@ public override long Seek(long offset, SeekOrigin origin) } this.currentOffset = newOffset; - this.currentPageOffset = newOffset; + this.currentBlobOffset = newOffset; return this.currentOffset; } @@ -235,8 +247,8 @@ public async Task CommitAsync() { if (this.blobMD5 != null) { - this.pageBlob.Properties.ContentMD5 = this.blobMD5.ComputeHash(); - await this.pageBlob.SetPropertiesAsync(this.accessCondition, this.options, this.operationContext); + this.Blob.Properties.ContentMD5 = this.blobMD5.ComputeHash(); + await this.Blob.SetPropertiesAsync(this.accessCondition, this.options, this.operationContext); } } } @@ -276,7 +288,7 @@ private async Task DispatchWriteAsync() this.blockList.Add(blockId); await this.WriteBlockAsync(bufferToUpload, blockId, bufferMD5); } - else + else if (this.pageBlob != null) { if ((bufferToUpload.Length % Constants.PageSize) != 0) { @@ -284,10 +296,26 @@ private async Task DispatchWriteAsync() throw this.lastException; } - long offset = this.currentPageOffset; - this.currentPageOffset += bufferToUpload.Length; + long offset = this.currentBlobOffset; + this.currentBlobOffset += bufferToUpload.Length; await this.WritePagesAsync(bufferToUpload, offset, bufferMD5); } + else + { + long offset = this.currentBlobOffset; + this.currentBlobOffset += bufferToUpload.Length; + + // We cannot differentiate between max size condition failing only in the retry versus failing in the + // first attempt and retry even for a single writer scenario. So we will eliminate the latter and handle + // the former in the append operation call. + if (this.accessCondition.IfMaxSizeLessThanOrEqual.HasValue && this.currentBlobOffset > this.accessCondition.IfMaxSizeLessThanOrEqual.Value) + { + this.lastException = new IOException(SR.InvalidBlockSize); + throw this.lastException; + } + + await this.WriteAppendBlockAsync(bufferToUpload, offset, bufferMD5); + } } /// @@ -335,5 +363,54 @@ private async Task WritePagesAsync(Stream pageData, long offset, string contentM this.parallelOperationSemaphore.Release(); }); } + + /// + /// Starts an asynchronous AppendBlock operation as soon as the parallel + /// operation semaphore becomes available. Since parallelism is always set + /// to 1 for append blobs, appendblock operations are called serially. + /// + /// Data to be uploaded + /// Offset within the append blob to be used to set the append offset conditional header. + /// MD5 hash of the data to be uploaded + /// A task that represents the asynchronous write operation. + private async Task WriteAppendBlockAsync(Stream blockData, long offset, string blockMD5) + { + this.noPendingWritesEvent.Increment(); + await this.parallelOperationSemaphore.WaitAsync(); + + this.accessCondition.IfAppendPositionEqual = offset; + + int previousResultsCount = this.operationContext.RequestResults.Count; + Task writeBlockTask = this.appendBlob.AppendBlockAsync(blockData.AsInputStream(), blockMD5, this.accessCondition, this.options, this.operationContext).AsTask().ContinueWith(task => + { + if (task.Exception != null) + { + if (this.options.AbsorbConditionalErrorsOnRetry.Value + && this.operationContext.LastResult.HttpStatusCode == (int)HttpStatusCode.PreconditionFailed) + { + StorageExtendedErrorInformation extendedInfo = this.operationContext.LastResult.ExtendedErrorInformation; + if (extendedInfo != null + && (extendedInfo.ErrorCode == BlobErrorCodeStrings.InvalidAppendCondition || extendedInfo.ErrorCode == BlobErrorCodeStrings.InvalidMaxBlobSizeCondition) + && (this.operationContext.RequestResults.Count - previousResultsCount > 1)) + { + // Pre-condition failure on a retry should be ignored in a single writer scenario since the request + // succeeded in the first attempt. + Logger.LogWarning(this.operationContext, SR.PreconditionFailureIgnored); + } + else + { + this.lastException = task.Exception; + } + } + else + { + this.lastException = task.Exception; + } + } + + this.noPendingWritesEvent.Decrement(); + this.parallelOperationSemaphore.Release(); + }); + } } } \ No newline at end of file diff --git a/Lib/WindowsRuntime/Blob/BlobWriteStreamHelper.cs b/Lib/WindowsRuntime/Blob/BlobWriteStreamHelper.cs index 7ffc8c055..778b2b79b 100644 --- a/Lib/WindowsRuntime/Blob/BlobWriteStreamHelper.cs +++ b/Lib/WindowsRuntime/Blob/BlobWriteStreamHelper.cs @@ -59,6 +59,18 @@ internal BlobWriteStreamHelper(CloudPageBlob pageBlob, long pageBlobSize, bool c this.originalStreamAsOutputStream = this.originalStream.AsOutputStream(); } + /// + /// Initializes a new instance of the BlobWriteStreamHelper class for an append blob. + /// + /// Blob reference to write to. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// An object that specifies additional options for the request. + internal BlobWriteStreamHelper(CloudAppendBlob appendBlob, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + this.originalStream = new BlobWriteStream(appendBlob, accessCondition, options, operationContext); + this.originalStreamAsOutputStream = this.originalStream.AsOutputStream(); + } + /// /// Gets a value that indicates whether the stream can be read from. /// diff --git a/Lib/WindowsRuntime/Blob/CloudAppendBlob.cs b/Lib/WindowsRuntime/Blob/CloudAppendBlob.cs new file mode 100644 index 000000000..b6fe39263 --- /dev/null +++ b/Lib/WindowsRuntime/Blob/CloudAppendBlob.cs @@ -0,0 +1,1557 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.Blob +{ + using Microsoft.WindowsAzure.Storage.Blob.Protocol; + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Core.Executor; + using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using System.Net.Http; + using System.Text; + +#if ASPNET_K || PORTABLE + using System.Threading; + using System.Threading.Tasks; +#else + using System.Runtime.InteropServices.WindowsRuntime; + using Windows.Foundation; + using Windows.Storage.Streams; + using Windows.Storage; +#endif + + /// + /// Represents an append blob, a type of blob where blocks of data are always committed to the end of the blob. + /// + public sealed partial class CloudAppendBlob : CloudBlob, ICloudBlob + { + /// + /// Opens a stream for writing to the blob. + /// + /// Use true to create a new append blob or overwrite an existing one, false to append to an existing blob. + /// A stream to be used for writing to the blob. + [DoesServiceRequest] +#if ASPNET_K || PORTABLE + public Task OpenWriteAsync(bool createNew) +#else + public IAsyncOperation OpenWriteAsync(bool createNew) +#endif + { + return this.OpenWriteAsync(createNew, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Opens a stream for writing to the blob. + /// + /// Use true to create a new append blob or overwrite an existing one, false to append to an existing blob. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// Note that this method always makes a call to the method under the covers. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// A stream to be used for writing to the blob. + [DoesServiceRequest] +#if ASPNET_K || PORTABLE + public Task OpenWriteAsync(bool createNew, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.OpenWriteAsync(createNew, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Opens a stream for writing to the blob. + /// + /// Use true if the append blob is newly created, false otherwise. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A stream to be used for writing to the blob. + [DoesServiceRequest] + public Task OpenWriteAsync(bool createNew, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) +#else + public IAsyncOperation OpenWriteAsync(bool createNew, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) +#endif + { + this.attributes.AssertNoSnapshot(); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient, false); + if (!createNew && modifiedOptions.StoreBlobContentMD5.Value) + { + throw new ArgumentException(SR.MD5NotPossible); + } + +#if ASPNET_K || PORTABLE + return Task.Run(async () => +#else + return AsyncInfo.Run(async (token) => +#endif + { + if (createNew) + { +#if ASPNET_K || PORTABLE + await this.CreateOrReplaceAsync(accessCondition, options, operationContext, cancellationToken); +#else + await this.CreateOrReplaceAsync(accessCondition, options, operationContext).AsTask(token); +#endif + } + else + { + // Although we don't need any properties from the service, we should make this call in order to honor the user specified conditional headers + // while opening an existing stream and to get the append position for an existing blob if user didn't specify one. +#if ASPNET_K || PORTABLE + await this.FetchAttributesAsync(accessCondition, options, operationContext, cancellationToken); +#else + await this.FetchAttributesAsync(accessCondition, options, operationContext).AsTask(token); +#endif + } + + if (accessCondition != null) + { + accessCondition = new AccessCondition() { LeaseId = accessCondition.LeaseId, IfAppendPositionEqual = accessCondition.IfAppendPositionEqual, IfMaxSizeLessThanOrEqual = accessCondition.IfMaxSizeLessThanOrEqual }; + } + +#if ASPNET_K || PORTABLE + CloudBlobStream stream = new BlobWriteStream(this, accessCondition, modifiedOptions, operationContext); +#else + ICloudBlobStream stream = new BlobWriteStreamHelper(this, accessCondition, modifiedOptions, operationContext); +#endif + return stream; +#if ASPNET_K || PORTABLE + }, cancellationToken); +#else + }); +#endif + } + + /// + /// Uploads a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// The stream providing the blob content. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task UploadFromStreamAsync(Stream source) +#else + /// An that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public IAsyncAction UploadFromStreamAsync(IInputStream source) +#endif + { + return this.UploadFromStreamAsyncHelper(source, null /* length */, true /* createNew */, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Uploads a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// The stream providing the blob content. + /// The number of bytes to write from the source stream at its current position. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromStreamAsync(Stream source, long length) +#else + /// An that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public IAsyncAction UploadFromStreamAsync(IInputStream source, long length) +#endif + { + return this.UploadFromStreamAsyncHelper(source, length, true /* createNew */, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Uploads a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// The stream providing the blob content. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) +#else + /// An that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public IAsyncAction UploadFromStreamAsync(IInputStream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) +#endif + { + return this.UploadFromStreamAsyncHelper(source, null /* length */, true /* createNew */, accessCondition, options, operationContext); + } + + /// + /// Uploads a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// The stream providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) +#else + /// An that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public IAsyncAction UploadFromStreamAsync(IInputStream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) +#endif + { + return this.UploadFromStreamAsyncHelper(source, length, true /* createNew */, accessCondition, options, operationContext); + } + +#if ASPNET_K || PORTABLE + /// + /// Uploads a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// The stream providing the blob content. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.UploadFromStreamAsyncHelper(source, null /* length */, true /* createNew */, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Uploads a stream to an append blob. If the blob already exists, it will be overwritten. Recommended only for single-writer scenarios. + /// + /// The stream providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.UploadFromStreamAsyncHelper(source, length, true /* createNew */, accessCondition, options, operationContext, cancellationToken); + } +#endif + + /// + /// Appends a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// The stream providing the blob content. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromStreamAsync(Stream source) +#else + /// An that represents an asynchronous action. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public IAsyncAction AppendFromStreamAsync(IInputStream source) +#endif + { + return this.UploadFromStreamAsyncHelper(source, null /* length */, false /* createNew */, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Appends a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// The stream providing the blob content. + /// The number of bytes to write from the source stream at its current position. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// + [DoesServiceRequest] + public Task AppendFromStreamAsync(Stream source, long length) +#else + /// An that represents an asynchronous action. + [DoesServiceRequest] + public IAsyncAction AppendFromStreamAsync(IInputStream source, long length) +#endif + { + return this.UploadFromStreamAsyncHelper(source, length, false /* createNew */, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Appends a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// The stream providing the blob content. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) +#else + /// An that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public IAsyncAction AppendFromStreamAsync(IInputStream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) +#endif + { + return this.UploadFromStreamAsyncHelper(source, null /* length */, false /* createNew */, accessCondition, options, operationContext); + } + + /// + /// Appends a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// The stream providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) +#else + /// An that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public IAsyncAction AppendFromStreamAsync(IInputStream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) +#endif + { + return this.UploadFromStreamAsyncHelper(source, length, false /* createNew */, accessCondition, options, operationContext); + } + +#if ASPNET_K || PORTABLE + /// + /// Appends a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// The stream providing the blob content. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.UploadFromStreamAsyncHelper(source, null /* length */, false /* createNew */, accessCondition, options, operationContext, cancellationToken); + } + + /// + /// Appends a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// The stream providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.UploadFromStreamAsyncHelper(source, length, false /* createNew */, accessCondition, options, operationContext, cancellationToken); + } +#endif + + /// + /// Uploads a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// The stream providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// true if the append blob is newly created, false otherwise. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + [DoesServiceRequest] + internal Task UploadFromStreamAsyncHelper(Stream source, long? length, bool createNew, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return UploadFromStreamAsyncHelper(source, length, createNew, accessCondition, options, operationContext, CancellationToken.None); + } + + /// + /// Uploads a stream to an append blob. Recommended only for single-writer scenarios. + /// + /// The stream providing the blob content. + /// The number of bytes to write from the source stream at its current position. + /// true if the append blob is newly created, false otherwise. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + [DoesServiceRequest] + internal Task UploadFromStreamAsyncHelper(Stream source, long? length, bool createNew, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) +#else + /// An that represents an asynchronous action. + [DoesServiceRequest] + internal IAsyncAction UploadFromStreamAsyncHelper(IInputStream source, long? length, bool createNew, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) +#endif + { + CommonUtility.AssertNotNull("source", source); + + Stream sourceAsStream = source.AsStreamForRead(); + if (length.HasValue) + { + CommonUtility.AssertInBounds("length", length.Value, 1); + + if (sourceAsStream.CanSeek && length > sourceAsStream.Length - sourceAsStream.Position) + { + throw new ArgumentOutOfRangeException("length", SR.StreamLengthShortError); + } + } + + this.attributes.AssertNoSnapshot(); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); + operationContext = operationContext ?? new OperationContext(); + ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions); + +#if ASPNET_K || PORTABLE + return Task.Run(async () => + { + using (CloudBlobStream blobStream = await this.OpenWriteAsync(createNew, accessCondition, options, operationContext, cancellationToken)) + { + // We should always call AsStreamForWrite with bufferSize=0 to prevent buffering. Our + // stream copier only writes 64K buffers at a time anyway, so no buffering is needed. + await sourceAsStream.WriteToAsync(blobStream, length, null /* maxLength */, false, tempExecutionState, null /* streamCopyState */, cancellationToken); + await blobStream.CommitAsync(); + } + }, cancellationToken); +#else + return AsyncInfo.Run(async (token) => + { + using (ICloudBlobStream blobStream = await this.OpenWriteAsync(createNew, accessCondition, options, operationContext).AsTask(token)) + { + // We should always call AsStreamForWrite with bufferSize=0 to prevent buffering. Our + // stream copier only writes 64K buffers at a time anyway, so no buffering is needed. + await sourceAsStream.WriteToAsync(blobStream.AsStreamForWrite(0), length, null /* maxLength */, false, tempExecutionState, null /* streamCopyState */, token); + await blobStream.CommitAsync().AsTask(token); + } + }); +#endif + } + +#if !PORTABLE + /// + /// Uploads a file to an append blob. If the blob already exists, it will be overwritten. + /// +#if ASPNET_K + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromFileAsync(string path, FileMode mode) + { + return this.UploadFromFileAsync(path, mode, null /* accessCondition */, null /* options */, null /* operationContext */); + } +#else + /// The file providing the blob content. + /// An that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public IAsyncAction UploadFromFileAsync(StorageFile source) + { + return this.UploadFromFileAsync(source, null /* accessCondition */, null /* options */, null /* operationContext */); + } +#endif + + /// + /// Uploads a file to an append blob. If the blob already exists, it will be overwritten. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. +#if ASPNET_K + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.UploadFromFileAsync(path, mode, accessCondition, options, operationContext, CancellationToken.None); + } +#else + /// An that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public IAsyncAction UploadFromFileAsync(StorageFile source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + CommonUtility.AssertNotNull("source", source); + + return AsyncInfo.Run(async (token) => + { + using (IRandomAccessStreamWithContentType stream = await source.OpenReadAsync().AsTask(token)) + { + await this.UploadFromStreamAsync(stream, accessCondition, options, operationContext).AsTask(token); + } + }); + } +#endif + +#if ASPNET_K + /// + /// Uploads a file to an append blob. If the blob already exists, it will be overwritten. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + CommonUtility.AssertNotNull("path", path); + + return Task.Run(async () => + { + using (Stream stream = new FileStream(path, mode, FileAccess.Read)) + { + await this.UploadFromStreamAsync(stream, accessCondition, options, operationContext, cancellationToken); + } + }, cancellationToken); + } +#endif + + /// + /// Appends a file to an append blob. Recommended only for single-writer scenarios. + /// +#if ASPNET_K + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// A that represents an asynchronous action. + [DoesServiceRequest] + public Task AppendFromFileAsync(string path, FileMode mode) + { + return this.AppendFromFileAsync(path, mode, null /* accessCondition */, null /* options */, null /* operationContext */); + } +#else + /// The file providing the blob content. + /// An that represents an asynchronous action. + [DoesServiceRequest] + public IAsyncAction AppendFromFileAsync(StorageFile source) + { + return this.AppendFromFileAsync(source, null /* accessCondition */, null /* options */, null /* operationContext */); + } +#endif + + /// + /// Appends a file to an append blob. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. +#if ASPNET_K + /// A that represents an asynchronous action. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.AppendFromFileAsync(path, mode, accessCondition, options, operationContext, CancellationToken.None); + } +#else + /// An that represents an asynchronous action. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public IAsyncAction AppendFromFileAsync(StorageFile source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + CommonUtility.AssertNotNull("source", source); + + return AsyncInfo.Run(async (token) => + { + using (IRandomAccessStreamWithContentType stream = await source.OpenReadAsync().AsTask(token)) + { + await this.AppendFromStreamAsync(stream, accessCondition, options, operationContext).AsTask(token); + } + }); + } +#endif + +#if ASPNET_K + /// + /// Appends a file to an append blob. Recommended only for single-writer scenarios. + /// + /// A string containing the file path providing the blob content. + /// A enumeration value that specifies how to open the file. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + CommonUtility.AssertNotNull("path", path); + + return Task.Run(async () => + { + using (Stream stream = new FileStream(path, mode, FileAccess.Read)) + { + await this.AppendFromStreamAsync(stream, accessCondition, options, operationContext, cancellationToken); + } + }, cancellationToken); + } +#endif +#endif + + /// + /// Uploads the contents of a byte array to an append blob. If the blob already exists, it will be overwritten. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count) +#else + /// An that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public IAsyncAction UploadFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int index, int count) +#endif + { + return this.UploadFromByteArrayAsync(buffer, index, count, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Uploads the contents of a byte array to an append blob. If the blob already exists, it will be overwritten. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.UploadFromByteArrayAsync(buffer, index, count, accessCondition, options, operationContext, CancellationToken.None); + } +#else + /// An that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public IAsyncAction UploadFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + CommonUtility.AssertNotNull("buffer", buffer); + + SyncMemoryStream stream = new SyncMemoryStream(buffer, index, count); + return this.UploadFromStreamAsync(stream.AsInputStream(), accessCondition, options, operationContext); + } +#endif + +#if ASPNET_K || PORTABLE + /// + /// Uploads the contents of a byte array to an append blob. If the blob already exists, it will be overwritten. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + CommonUtility.AssertNotNull("buffer", buffer); + + SyncMemoryStream stream = new SyncMemoryStream(buffer, index, count); + return this.UploadFromStreamAsync(stream, accessCondition, options, operationContext, cancellationToken); + } +#endif + + /// + /// Appends the contents of a byte array to an append blob. Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + [DoesServiceRequest] + public Task AppendFromByteArrayAsync(byte[] buffer, int index, int count) +#else + /// An that represents an asynchronous action. + [DoesServiceRequest] + public IAsyncAction AppendFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int index, int count) +#endif + { + return this.AppendFromByteArrayAsync(buffer, index, count, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Appends the contents of a byte array to an append blob. Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.AppendFromByteArrayAsync(buffer, index, count, accessCondition, options, operationContext, CancellationToken.None); + } +#else + /// An that represents an asynchronous action. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public IAsyncAction AppendFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + CommonUtility.AssertNotNull("buffer", buffer); + + SyncMemoryStream stream = new SyncMemoryStream(buffer, index, count); + return this.AppendFromStreamAsync(stream.AsInputStream(), accessCondition, options, operationContext); + } +#endif + +#if ASPNET_K || PORTABLE + /// + /// Appends the contents of a byte array to an append blob. Recommended only for single-writer scenarios. + /// + /// An array of bytes. + /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. + /// The number of bytes to be written to the blob. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + CommonUtility.AssertNotNull("buffer", buffer); + + SyncMemoryStream stream = new SyncMemoryStream(buffer, index, count); + return this.AppendFromStreamAsync(stream, accessCondition, options, operationContext, cancellationToken); + } +#endif + + /// + /// Uploads a string of text to an append blob. If the blob already exists, it will be overwritten. + /// + /// The text to upload, encoded as a UTF-8 string. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadTextAsync(string content) +#else + /// An that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public IAsyncAction UploadTextAsync(string content) +#endif + { + return this.UploadTextAsync(content, null /* encoding */, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Uploads a string of text to an append blob. If the blob already exists, it will be overwritten. + /// + /// The text to upload. + /// A object that indicates the text encoding to use. If null, UTF-8 will be used. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadTextAsync(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.UploadTextAsync(content, encoding, accessCondition, options, operationContext, CancellationToken.None); + } +#else + /// An that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public IAsyncAction UploadTextAsync(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + CommonUtility.AssertNotNull("content", content); + + byte[] contentAsBytes = (encoding ?? Encoding.UTF8).GetBytes(content); + return this.UploadFromByteArrayAsync(contentAsBytes, 0, contentAsBytes.Length, accessCondition, options, operationContext); + } +#endif + +#if ASPNET_K || PORTABLE + /// + /// Uploads a string of text to an append blob. If the blob already exists, it will be overwritten. + /// + /// The text to upload. + /// A object that indicates the text encoding to use. If null, UTF-8 will be used. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// To append data to an append blob that already exists, see . + /// + [DoesServiceRequest] + public Task UploadTextAsync(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + CommonUtility.AssertNotNull("content", content); + + byte[] contentAsBytes = (encoding ?? Encoding.UTF8).GetBytes(content); + return this.UploadFromByteArrayAsync(contentAsBytes, 0, contentAsBytes.Length, accessCondition, options, operationContext, cancellationToken); + } +#endif + + /// + /// Appends a string of text to an append blob. + /// + /// The text to upload, encoded as a UTF-8 string. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + [DoesServiceRequest] + public Task AppendTextAsync(string content) +#else + /// An that represents an asynchronous action. + [DoesServiceRequest] + public IAsyncAction AppendTextAsync(string content) +#endif + { + return this.AppendTextAsync(content, null /* encoding */, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Appends a string of text to an append blob. Recommended only for single-writer scenarios. + /// + /// The text to upload. + /// A object that indicates the text encoding to use. If null, UTF-8 will be used. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendTextAsync(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.AppendTextAsync(content, encoding, accessCondition, options, operationContext, CancellationToken.None); + } +#else + /// An that represents an asynchronous action. + /// + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public IAsyncAction AppendTextAsync(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + CommonUtility.AssertNotNull("content", content); + + byte[] contentAsBytes = (encoding ?? Encoding.UTF8).GetBytes(content); + return this.AppendFromByteArrayAsync(contentAsBytes, 0, contentAsBytes.Length, accessCondition, options, operationContext); + } +#endif + +#if ASPNET_K || PORTABLE + /// + /// Appends a string of text to an append blob. + /// + /// The text to upload. + /// A object that indicates the text encoding to use. If null, UTF-8 will be used. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + /// + /// Use this method only in single-writer scenarios. Internally, this method uses the append-offset conditional header to avoid duplicate blocks, which may cause problems in multiple-writer scenarios. + /// If you have a single-writer scenario, see to determine whether setting this flag to true is acceptable for your scenario. + /// + [DoesServiceRequest] + public Task AppendTextAsync(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + CommonUtility.AssertNotNull("content", content); + + byte[] contentAsBytes = (encoding ?? Encoding.UTF8).GetBytes(content); + return this.AppendFromByteArrayAsync(contentAsBytes, 0, contentAsBytes.Length, accessCondition, options, operationContext, cancellationToken); + } +#endif + + /// + /// Creates an empty append blob. If the blob already exists, this operation will overwrite it. To throw an exception instead of overwriting the blob, + /// use . + /// + /// An that represents an asynchronous action. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + [DoesServiceRequest] + public Task CreateOrReplaceAsync() +#else + /// An that represents an asynchronous action. + [DoesServiceRequest] + public IAsyncAction CreateOrReplaceAsync() +#endif + { + return this.CreateOrReplaceAsync(null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Creates an empty append blob. If the blob already exists, this operation will overwrite it. To throw an exception instead of overwriting the blob, + /// pass in an object generated using . + /// + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + [DoesServiceRequest] + public Task CreateOrReplaceAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.CreateOrReplaceAsync(accessCondition, options, operationContext, CancellationToken.None); + } +#else + /// An that represents an asynchronous action. + [DoesServiceRequest] + public IAsyncAction CreateOrReplaceAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); + return AsyncInfo.Run(async (token) => await Executor.ExecuteAsyncNullReturn( + this.CreateImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + token)); + } +#endif + +#if ASPNET_K || PORTABLE + /// + /// Creates an empty append blob. If the blob already exists, this operation will overwrite it. To throw an exception instead of overwriting the blob, + /// pass in an object generated using . + /// + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + [DoesServiceRequest] + public Task CreateOrReplaceAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); + return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( + this.CreateImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken), cancellationToken); + } +#endif + + /// + /// Commits a new block of data to the end of the blob. + /// + /// A stream that provides the data for the block. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + [DoesServiceRequest] + public Task AppendBlockAsync(Stream blockData) +#else + /// An that represents an asynchronous action. + [DoesServiceRequest] + public IAsyncOperation AppendBlockAsync(IInputStream blockData) +#endif + { + return this.AppendBlockAsync(blockData, null /* contentMD5 */, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Commits a new block of data to the end of the blob. + /// + /// A stream that provides the data for the block. + /// An optional hash value that will be used to set the property + /// on the blob. May be null or an empty string. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + [DoesServiceRequest] + public Task AppendBlockAsync(Stream blockData, string contentMD5) +#else + /// An that represents an asynchronous action. + [DoesServiceRequest] + public IAsyncOperation AppendBlockAsync(IInputStream blockData, string contentMD5) +#endif + { + return this.AppendBlockAsync(blockData, contentMD5, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Commits a new block of data to the end of the blob. + /// + /// A stream that provides the data for the block. + /// An optional hash value that will be used to set the property + /// on the blob. May be null or an empty string. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. +#if ASPNET_K || PORTABLE + /// A that represents an asynchronous action. + public Task AppendBlockAsync(Stream blockData, string contentMD5, AccessCondition accesscondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.AppendBlockAsync(blockData, contentMD5, accesscondition, options, operationContext, CancellationToken.None); + } + + /// + /// Commits a new block of data to the end of the blob. + /// + /// A stream that provides the data for the block. + /// An optional hash value that will be used to set the property + /// on the blob. May be null or an empty string. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + [DoesServiceRequest] + public Task AppendBlockAsync(Stream blockData, string contentMD5, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) +#else + /// An that represents an asynchronous action. + [DoesServiceRequest] + public IAsyncOperation AppendBlockAsync(IInputStream blockData, string contentMD5, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) +#endif + { + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); + bool requiresContentMD5 = string.IsNullOrEmpty(contentMD5) && modifiedOptions.UseTransactionalMD5.Value; + operationContext = operationContext ?? new OperationContext(); + ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions); + +#if ASPNET_K || PORTABLE + return Task.Run(async () => +#else + return AsyncInfo.Run(async (cancellationToken) => +#endif + { + Stream blockDataAsStream = blockData.AsStreamForRead(); + Stream seekableStream = blockDataAsStream; + bool seekableStreamCreated = false; + + try + { + if (!blockDataAsStream.CanSeek || requiresContentMD5) + { + Stream writeToStream; + if (blockDataAsStream.CanSeek) + { + writeToStream = Stream.Null; + } + else + { + seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); + seekableStreamCreated = true; + writeToStream = seekableStream; + } + + StreamDescriptor streamCopyState = new StreamDescriptor(); + long startPosition = seekableStream.Position; + await blockDataAsStream.WriteToAsync(writeToStream, null /* copyLength */, Constants.MaxBlockSize, requiresContentMD5, tempExecutionState, streamCopyState, cancellationToken); + seekableStream.Position = startPosition; + + if (requiresContentMD5) + { + contentMD5 = streamCopyState.Md5; + } + } + + return await Executor.ExecuteAsync( + this.AppendBlockImpl(seekableStream, contentMD5, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken); + } + finally + { + if (seekableStreamCreated) + { + seekableStream.Dispose(); + } + } +#if ASPNET_K || PORTABLE + }, cancellationToken); +#else + }); +#endif + } + + /// + /// Downloads the blob's contents as a string. + /// + /// The contents of the blob, as a string. +#if ASPNET_K || PORTABLE + public Task DownloadTextAsync() +#else + public IAsyncOperation DownloadTextAsync() +#endif + { + return this.DownloadTextAsync(null /* encoding */, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Downloads the blob's contents as a string. + /// + /// An object that indicates the text encoding to use. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// The contents of the blob, as a string. +#if ASPNET_K || PORTABLE + public Task DownloadTextAsync(Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.DownloadTextAsync(encoding, accessCondition, options, operationContext, CancellationToken.None); + } +#else + public IAsyncOperation DownloadTextAsync(Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return AsyncInfo.Run(async (token) => + { + using (SyncMemoryStream stream = new SyncMemoryStream()) + { + await this.DownloadToStreamAsync(stream.AsOutputStream(), accessCondition, options, operationContext).AsTask(token); + byte[] streamAsBytes = stream.ToArray(); + return (encoding ?? Encoding.UTF8).GetString(streamAsBytes, 0, streamAsBytes.Length); + } + }); + } +#endif + +#if ASPNET_K || PORTABLE + /// + /// Downloads the blob's contents as a string. + /// + /// An object that indicates the text encoding to use. + /// An object that represents the access conditions for the blob. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// The contents of the blob, as a string. + public Task DownloadTextAsync(Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return Task.Run(async () => + { + using (SyncMemoryStream stream = new SyncMemoryStream()) + { + await this.DownloadToStreamAsync(stream, accessCondition, options, operationContext, cancellationToken); + byte[] streamAsBytes = stream.ToArray(); + return (encoding ?? Encoding.UTF8).GetString(streamAsBytes, 0, streamAsBytes.Length); + } + }, cancellationToken); + } +#endif + + /// + /// Begins an operation to start copying an existing block blob's contents, properties, and metadata to a new blob. + /// + /// The source blob. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the blob's ETag, last modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] +#if ASPNET_K || PORTABLE + public Task StartCopyAsync(CloudAppendBlob source) +#else + public IAsyncOperation StartCopyAsync(CloudAppendBlob source) +#endif + { + return this.StartCopyAsync(CloudBlob.SourceBlobToUri(source)); + } + + /// + /// Begins an operation to start copying another block blob's contents, properties, and metadata to a new blob. + /// + /// The source blob. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the blob's ETag, last modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] +#if ASPNET_K || PORTABLE + public Task StartCopyAsync(CloudAppendBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.StartCopyAsync(source, sourceAccessCondition, destAccessCondition, options, operationContext, CancellationToken.None); + } +#else + public IAsyncOperation StartCopyAsync(CloudAppendBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.StartCopyAsync(CloudBlob.SourceBlobToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext); + } +#endif + +#if ASPNET_K || PORTABLE + /// + /// Begins an operation to start copying another append blob's contents, properties, and metadata to a new blob. + /// + /// The source blob. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the blob's ETag, last modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] + public Task StartCopyAsync(CloudAppendBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.StartCopyAsync(CloudBlob.SourceBlobToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); + } +#endif + + /// + /// Creates a snapshot of the blob. + /// + /// A blob snapshot. + [DoesServiceRequest] +#if ASPNET_K || PORTABLE + public Task CreateSnapshotAsync() +#else + public IAsyncOperation CreateSnapshotAsync() +#endif + { + return this.CreateSnapshotAsync(null /* metadata */, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Creates a snapshot of the blob. + /// + /// A collection of name-value pairs defining the metadata of the snapshot. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// An object that specifies additional options for the request, or null. + /// An object that represents the context for the current operation. + /// A blob snapshot. + [DoesServiceRequest] +#if ASPNET_K || PORTABLE + public Task CreateSnapshotAsync(IDictionary metadata, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return CreateSnapshotAsync(metadata, accessCondition, options, operationContext, CancellationToken.None); + } +#else + public IAsyncOperation CreateSnapshotAsync(IDictionary metadata, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) + { + this.attributes.AssertNoSnapshot(); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); + return AsyncInfo.Run(async (token) => await Executor.ExecuteAsync( + this.CreateSnapshotImpl(metadata, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + token)); + } +#endif + +#if ASPNET_K || PORTABLE + /// + /// Creates a snapshot of the blob. + /// + /// A collection of name-value pairs defining the metadata of the snapshot. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// An object that specifies additional options for the request, or null. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A blob snapshot. + [DoesServiceRequest] + public Task CreateSnapshotAsync(IDictionary metadata, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + this.attributes.AssertNoSnapshot(); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); + return Task.Run(async () => await Executor.ExecuteAsync( + this.CreateSnapshotImpl(metadata, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken), cancellationToken); + } +#endif + + /// + /// Implements the Create method. + /// + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// A that creates the blob. + private RESTCommand CreateImpl(AccessCondition accessCondition, BlobRequestOptions options) + { + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.Handler = this.ServiceClient.AuthenticationHandler; + putCmd.BuildClient = HttpClientFactory.BuildHttpClient; + putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => + { + HttpRequestMessage msg = BlobHttpRequestMessageFactory.Put(uri, serverTimeout, this.Properties, BlobType.AppendBlob, 0, accessCondition, cnt, ctx); + BlobHttpRequestMessageFactory.AddMetadata(msg, this.Metadata); + return msg; + }; + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); + CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); + this.Properties.Length = 0; + return NullType.Value; + }; + + return putCmd; + } + + /// + /// Commits the block to the end of the blob. + /// + /// The source stream. + /// The content MD5. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// A that uploads the block. + internal RESTCommand AppendBlockImpl(Stream source, string contentMD5, AccessCondition accessCondition, BlobRequestOptions options) + { + long offset = source.Position; + long length = source.Length - offset; + + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.Handler = this.ServiceClient.AuthenticationHandler; + putCmd.BuildClient = HttpClientFactory.BuildHttpClient; + putCmd.BuildContent = (cmd, ctx) => HttpContentFactory.BuildContentFromStream(source, offset, length, contentMD5, cmd, ctx); + putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => BlobHttpRequestMessageFactory.AppendBlock(uri, serverTimeout, accessCondition, cnt, ctx); + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + long appendOffset = -1; + if (resp.Headers.Contains(Constants.HeaderConstants.BlobAppendOffset)) + { + appendOffset = long.Parse(resp.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.BlobAppendOffset)); + } + + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, appendOffset, cmd, ex); + CloudBlob.UpdateETagLMTLengthAndSequenceNumber(this.attributes, resp, false); + return appendOffset; + }; + + return putCmd; + } + + /// + /// Implementation for the CreateSnapshot method. + /// + /// A collection of name-value pairs defining the metadata of the snapshot, or null. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// A that creates the snapshot. + /// If the metadata parameter is null then no metadata is associated with the request. + internal RESTCommand CreateSnapshotImpl(IDictionary metadata, AccessCondition accessCondition, BlobRequestOptions options) + { + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.Handler = this.ServiceClient.AuthenticationHandler; + putCmd.BuildClient = HttpClientFactory.BuildHttpClient; + putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => + { + HttpRequestMessage msg = BlobHttpRequestMessageFactory.Snapshot(uri, serverTimeout, accessCondition, cnt, ctx); + if (metadata != null) + { + BlobHttpRequestMessageFactory.AddMetadata(msg, metadata); + } + + return msg; + }; + + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, null /* retVal */, cmd, ex); + DateTimeOffset snapshotTime = NavigationHelper.ParseSnapshotTime(BlobHttpResponseParsers.GetSnapshotTime(resp)); + CloudAppendBlob snapshot = new CloudAppendBlob(this.Name, snapshotTime, this.Container); + snapshot.attributes.Metadata = new Dictionary(metadata ?? this.Metadata); + snapshot.attributes.Properties = new BlobProperties(this.Properties); + CloudBlob.UpdateETagLMTLengthAndSequenceNumber(snapshot.attributes, resp, false); + return snapshot; + }; + + return putCmd; + } + } +} \ No newline at end of file diff --git a/Lib/WindowsRuntime/Blob/CloudBlob.cs b/Lib/WindowsRuntime/Blob/CloudBlob.cs index 6068ce44a..941ee92ed 100644 --- a/Lib/WindowsRuntime/Blob/CloudBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudBlob.cs @@ -38,7 +38,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob #endif /// - /// Represents a blob object. + /// Represents a blob. /// public partial class CloudBlob : IListBlobItem { @@ -109,7 +109,7 @@ public Task OpenReadAsync(AccessCondition accessCondition, BlobRequestOp /// /// The target stream. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadToStreamAsync(Stream target) #else @@ -129,7 +129,7 @@ public IAsyncAction DownloadToStreamAsync(IOutputStream target) /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadToStreamAsync(Stream target, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) #else @@ -150,7 +150,7 @@ public IAsyncAction DownloadToStreamAsync(IOutputStream target, AccessCondition /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadToStreamAsync(Stream target, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -162,10 +162,10 @@ public Task DownloadToStreamAsync(Stream target, AccessCondition accessCondition /// /// Downloads the contents of a blob to a file. /// -#if ASPNET_K +#if ASPNET_K /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadToFileAsync(string path, FileMode mode) { @@ -190,7 +190,7 @@ public IAsyncAction DownloadToFileAsync(StorageFile target) /// An object that represents the access conditions for the blob. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadToFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -243,7 +243,7 @@ public Task DownloadToFileAsync(string path, FileMode mode, AccessCondition acce }, cancellationToken); } #endif -#endif +#endif /// /// Downloads the contents of a blob to a byte array. @@ -305,7 +305,7 @@ public Task DownloadToByteArrayAsync(byte[] target, int index, AccessCondit /// The offset at which to begin downloading the blob, in bytes. /// The length of the data to download from the blob, in bytes. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadRangeToStreamAsync(Stream target, long? offset, long? length) #else @@ -327,7 +327,7 @@ public IAsyncAction DownloadRangeToStreamAsync(IOutputStream target, long? offse /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadRangeToStreamAsync(Stream target, long? offset, long? length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -363,7 +363,7 @@ public IAsyncAction DownloadRangeToStreamAsync(IOutputStream target, long? offse /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadRangeToStreamAsync(Stream target, long? offset, long? length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -456,7 +456,6 @@ public Task DownloadRangeToByteArrayAsync(byte[] target, int index, long? b }, cancellationToken); } #endif - /// /// Checks existence of the blob. /// @@ -540,7 +539,7 @@ private IAsyncOperation ExistsAsync(bool primaryOnly, BlobRequestOptions o /// Populates a blob's properties and metadata. /// #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task FetchAttributesAsync() #else @@ -559,7 +558,7 @@ public IAsyncAction FetchAttributesAsync() /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task FetchAttributesAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -587,7 +586,7 @@ public IAsyncAction FetchAttributesAsync(AccessCondition accessCondition, BlobRe /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task FetchAttributesAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -601,7 +600,7 @@ public Task FetchAttributesAsync(AccessCondition accessCondition, BlobRequestOpt #endif #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetMetadataAsync() #else @@ -620,7 +619,7 @@ public IAsyncAction SetMetadataAsync() /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetMetadataAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -649,7 +648,7 @@ public IAsyncAction SetMetadataAsync(AccessCondition accessCondition, BlobReques /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetMetadataAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -667,7 +666,7 @@ public Task SetMetadataAsync(AccessCondition accessCondition, BlobRequestOptions /// Updates the blob's properties. /// #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetPropertiesAsync() #else @@ -686,7 +685,7 @@ public IAsyncAction SetPropertiesAsync() /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetPropertiesAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -732,7 +731,7 @@ public Task SetPropertiesAsync(AccessCondition accessCondition, BlobRequestOptio /// Deletes the blob. /// #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteAsync() #else @@ -752,7 +751,7 @@ public IAsyncAction DeleteAsync() /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteAsync(DeleteSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -781,7 +780,7 @@ public IAsyncAction DeleteAsync(DeleteSnapshotsOption deleteSnapshotsOption, Acc /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteAsync(DeleteSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -965,7 +964,7 @@ public Task AcquireLeaseAsync(TimeSpan? leaseTime, string proposedLeaseI /// /// An object that represents the access conditions for the blob, including a required lease ID. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task RenewLeaseAsync(AccessCondition accessCondition) #else @@ -984,7 +983,7 @@ public IAsyncAction RenewLeaseAsync(AccessCondition accessCondition) /// The options for this operation. If null, default options will be used. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task RenewLeaseAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -1012,7 +1011,7 @@ public IAsyncAction RenewLeaseAsync(AccessCondition accessCondition, BlobRequest /// The options for this operation. If null, default options will be used. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task RenewLeaseAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -1094,7 +1093,7 @@ public Task ChangeLeaseAsync(string proposedLeaseId, AccessCondition acc /// /// An object that represents the access conditions for the blob, including a required lease ID. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ReleaseLeaseAsync(AccessCondition accessCondition) #else @@ -1113,7 +1112,7 @@ public IAsyncAction ReleaseLeaseAsync(AccessCondition accessCondition) /// The options for this operation. If null, default options will be used. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ReleaseLeaseAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -1141,7 +1140,7 @@ public IAsyncAction ReleaseLeaseAsync(AccessCondition accessCondition, BlobReque /// The options for this operation. If null, default options will be used. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ReleaseLeaseAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -1296,6 +1295,7 @@ public Task SnapshotAsync(IDictionary metadata, Acces /// This method fetches the blob's ETag, last modified time, and part of the copy state. /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. /// + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] #if ASPNET_K || PORTABLE public Task StartCopyFromBlobAsync(Uri source) @@ -1316,6 +1316,7 @@ public IAsyncOperation StartCopyFromBlobAsync(Uri source) /// This method fetches the blob's ETag, last modified time, and part of the copy state. /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. /// + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] #if ASPNET_K || PORTABLE public Task StartCopyFromBlobAsync(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) @@ -1345,6 +1346,7 @@ public IAsyncOperation StartCopyFromBlobAsync(Uri source, AccessConditio /// This method fetches the blob's ETag, last modified time, and part of the copy state. /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. /// + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -1439,7 +1441,7 @@ public Task StartCopyAsync(Uri source, AccessCondition sourceAccessCondi /// /// A string identifying the copy operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task AbortCopyAsync(string copyId) #else @@ -1459,7 +1461,7 @@ public IAsyncAction AbortCopyAsync(string copyId) /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task AbortCopyAsync(string copyId, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -1489,11 +1491,11 @@ public IAsyncAction AbortCopyAsync(string copyId, AccessCondition accessConditio /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task AbortCopyAsync(string copyId, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { - BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.Unspecified, this.ServiceClient); + BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.AppendBlob, this.ServiceClient); return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( this.AbortCopyImpl(this.attributes, copyId, accessCondition, modifiedOptions), modifiedOptions.RetryPolicy, @@ -1505,6 +1507,7 @@ public Task AbortCopyAsync(string copyId, AccessCondition accessCondition, BlobR /// /// Implements getting the blob. /// + /// /// The blob's attributes. /// The target stream. /// The offset at which to begin downloading the blob, in bytes. @@ -2057,6 +2060,7 @@ internal static void UpdateETagLMTLengthAndSequenceNumber(BlobAttributes attribu attributes.Properties.ETag = parsedProperties.ETag ?? attributes.Properties.ETag; attributes.Properties.LastModified = parsedProperties.LastModified ?? attributes.Properties.LastModified; attributes.Properties.PageBlobSequenceNumber = parsedProperties.PageBlobSequenceNumber ?? attributes.Properties.PageBlobSequenceNumber; + attributes.Properties.AppendBlobCommittedBlockCount = parsedProperties.AppendBlobCommittedBlockCount ?? attributes.Properties.AppendBlobCommittedBlockCount; if (updateLength) { diff --git a/Lib/WindowsRuntime/Blob/CloudBlobClient.cs b/Lib/WindowsRuntime/Blob/CloudBlobClient.cs index 60e2a52f6..d7cced852 100644 --- a/Lib/WindowsRuntime/Blob/CloudBlobClient.cs +++ b/Lib/WindowsRuntime/Blob/CloudBlobClient.cs @@ -241,6 +241,7 @@ public IAsyncOperation ListBlobsSegmentedAsync(string prefix, /// The URI of the blob. /// A reference to the blob. [DoesServiceRequest] + #if ASPNET_K || PORTABLE public Task GetBlobReferenceFromServerAsync(Uri blobUri) #else @@ -321,7 +322,6 @@ public Task GetBlobReferenceFromServerAsync(StorageUri blobUri, Acce cancellationToken), cancellationToken); } #endif - /// /// Core implementation for the ListContainers method. /// @@ -374,6 +374,59 @@ private RESTCommand> ListContainersImpl(string return getCmd; } + /// + /// Implements the FetchAttributes method. The attributes are updated immediately. + /// + /// The URI of the blob. + /// An object that represents the access conditions for the blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// A that fetches the attributes. + private RESTCommand GetBlobReferenceFromServerImpl(StorageUri blobUri, AccessCondition accessCondition, BlobRequestOptions options) + { + // If the blob Uri contains SAS credentials, we need to use those + // credentials instead of this service client's stored credentials. + StorageCredentials parsedCredentials; + DateTimeOffset? parsedSnapshot; + blobUri = NavigationHelper.ParseBlobQueryAndVerify(blobUri, out parsedCredentials, out parsedSnapshot); + CloudBlobClient client = parsedCredentials != null ? new CloudBlobClient(this.StorageUri, parsedCredentials) : this; + + RESTCommand getCmd = new RESTCommand(client.Credentials, blobUri); + + options.ApplyToStorageCommand(getCmd); + getCmd.CommandLocationMode = CommandLocationMode.PrimaryOrSecondary; + getCmd.Handler = client.AuthenticationHandler; + getCmd.BuildClient = HttpClientFactory.BuildHttpClient; + getCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => BlobHttpRequestMessageFactory.GetProperties(uri, serverTimeout, parsedSnapshot, accessCondition, cnt, ctx); + getCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, null /* retVal */, cmd, ex); + BlobAttributes attributes = new BlobAttributes() + { + StorageUri = blobUri, + SnapshotTime = parsedSnapshot, + }; + + CloudBlob.UpdateAfterFetchAttributes(attributes, resp, false); + + switch (attributes.Properties.BlobType) + { + case BlobType.BlockBlob: + return new CloudBlockBlob(attributes, client); + + case BlobType.PageBlob: + return new CloudPageBlob(attributes, client); + + case BlobType.AppendBlob: + return new CloudAppendBlob(attributes, client); + + default: + throw new InvalidOperationException(); + } + }; + + return getCmd; + } + /// /// Implements the FetchAttributes method. The attributes are updated immediately. /// @@ -416,6 +469,9 @@ private RESTCommand GetBlobReferenceImpl(StorageUri blobUri, AccessC case BlobType.PageBlob: return new CloudPageBlob(attributes, client); + case BlobType.AppendBlob: + return new CloudAppendBlob(attributes, client); + default: throw new InvalidOperationException(); } @@ -592,6 +648,7 @@ private RESTCommand SetServicePropertiesImpl(ServiceProperties propert requestOptions.ApplyToStorageCommand(retCmd); retCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => BlobHttpRequestMessageFactory.SetServiceProperties(uri, serverTimeout, cnt, ctx); retCmd.BuildContent = (cmd, ctx) => HttpContentFactory.BuildContentFromStream(memoryStream, 0, memoryStream.Length, null /* md5 */, cmd, ctx); + retCmd.StreamToDispose = memoryStream; retCmd.RetrieveResponseStream = true; retCmd.Handler = this.AuthenticationHandler; retCmd.BuildClient = HttpClientFactory.BuildHttpClient; @@ -603,7 +660,7 @@ private RESTCommand SetServicePropertiesImpl(ServiceProperties propert } /// - /// Gets the stats of the blob service. + /// Gets service stats for the Blob service. /// /// The blob service stats. [DoesServiceRequest] @@ -617,7 +674,7 @@ public IAsyncOperation GetServiceStatsAsync() } /// - /// Gets the stats of the blob service. + /// Gets service stats for the Blob service. /// /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object that represents the context for the current operation. @@ -645,7 +702,7 @@ public IAsyncOperation GetServiceStatsAsync(BlobRequestOptions opt #if ASPNET_K /// - /// Gets the stats of the blob service. + /// Gets service stats for the Blob service. /// /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object that represents the context for the current operation. diff --git a/Lib/WindowsRuntime/Blob/CloudBlobContainer.cs b/Lib/WindowsRuntime/Blob/CloudBlobContainer.cs index 4f7ef1d7f..66048d98c 100644 --- a/Lib/WindowsRuntime/Blob/CloudBlobContainer.cs +++ b/Lib/WindowsRuntime/Blob/CloudBlobContainer.cs @@ -47,7 +47,7 @@ public sealed partial class CloudBlobContainer /// Creates the container. /// #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync() #else @@ -65,7 +65,7 @@ public IAsyncAction CreateAsync() /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync(BlobRequestOptions options, OperationContext operationContext) #else @@ -84,7 +84,7 @@ public IAsyncAction CreateAsync(BlobRequestOptions options, OperationContext ope /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync(BlobContainerPublicAccessType accessType, BlobRequestOptions options, OperationContext operationContext) { @@ -112,7 +112,7 @@ public IAsyncAction CreateAsync(BlobContainerPublicAccessType accessType, BlobRe /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync(BlobContainerPublicAccessType accessType, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -269,7 +269,7 @@ public Task CreateIfNotExistsAsync(BlobContainerPublicAccessType accessTyp /// Deletes the container. /// #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteAsync() #else @@ -288,7 +288,7 @@ public IAsyncAction DeleteAsync() /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -316,7 +316,7 @@ public IAsyncAction DeleteAsync(AccessCondition accessCondition, BlobRequestOpti /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -617,7 +617,7 @@ public Task ListBlobsSegmentedAsync(string prefix, bool useFl /// /// The permissions to apply to the container. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetPermissionsAsync(BlobContainerPermissions permissions) #else @@ -637,7 +637,7 @@ public IAsyncAction SetPermissionsAsync(BlobContainerPermissions permissions) /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetPermissionsAsync(BlobContainerPermissions permissions, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -666,7 +666,7 @@ public IAsyncAction SetPermissionsAsync(BlobContainerPermissions permissions, Ac /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetPermissionsAsync(BlobContainerPermissions permissions, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -835,7 +835,7 @@ public IAsyncAction FetchAttributesAsync() /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task FetchAttributesAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -863,7 +863,7 @@ public IAsyncAction FetchAttributesAsync(AccessCondition accessCondition, BlobRe /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task FetchAttributesAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -881,7 +881,7 @@ public Task FetchAttributesAsync(AccessCondition accessCondition, BlobRequestOpt /// Sets the container's user-defined metadata. /// #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetMetadataAsync() #else @@ -926,7 +926,7 @@ public IAsyncAction SetMetadataAsync(AccessCondition accessCondition, BlobReques /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetMetadataAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -1016,7 +1016,7 @@ public Task AcquireLeaseAsync(TimeSpan? leaseTime, string proposedLeaseI /// /// An object that represents the access conditions for the container, including a required lease ID. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task RenewLeaseAsync(AccessCondition accessCondition) #else @@ -1035,7 +1035,7 @@ public IAsyncAction RenewLeaseAsync(AccessCondition accessCondition) /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task RenewLeaseAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -1063,7 +1063,7 @@ public IAsyncAction RenewLeaseAsync(AccessCondition accessCondition, BlobRequest /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task RenewLeaseAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -1145,7 +1145,7 @@ public Task ChangeLeaseAsync(string proposedLeaseId, AccessCondition acc /// /// An object that represents the access conditions for the container, including a required lease ID. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ReleaseLeaseAsync(AccessCondition accessCondition) #else @@ -1164,7 +1164,7 @@ public IAsyncAction ReleaseLeaseAsync(AccessCondition accessCondition) /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. This object is used to track requests, and to provide additional runtime information about the operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ReleaseLeaseAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -1192,7 +1192,7 @@ public IAsyncAction ReleaseLeaseAsync(AccessCondition accessCondition, BlobReque /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. This object is used to track requests, and to provide additional runtime information about the operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ReleaseLeaseAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -1466,7 +1466,8 @@ private RESTCommand CreateContainerImpl(BlobRequestOptions options, Bl putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); - this.UpdateETagAndLastModified(resp); + this.Properties = ContainerHttpResponseParsers.GetProperties(resp); + this.Metadata = ContainerHttpResponseParsers.GetMetadata(resp); return NullType.Value; }; @@ -1491,7 +1492,7 @@ private RESTCommand DeleteContainerImpl(AccessCondition accessConditio return putCmd; } -#endif +#endif /// /// Implementation for the FetchAttributes method. @@ -1598,6 +1599,7 @@ private RESTCommand SetPermissionsImpl(BlobContainerPermissions acl, A putCmd.BuildClient = HttpClientFactory.BuildHttpClient; putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => ContainerHttpRequestMessageFactory.SetAcl(uri, serverTimeout, acl.PublicAccess, accessCondition, cnt, ctx); putCmd.BuildContent = (cmd, ctx) => HttpContentFactory.BuildContentFromStream(memoryStream, 0, memoryStream.Length, null /* md5 */, cmd, ctx); + putCmd.StreamToDispose = memoryStream; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); @@ -1668,6 +1670,10 @@ private IListBlobItem SelectListBlobItem(IListBlobEntry protocolItem) { return new CloudPageBlob(attributes, this.ServiceClient); } + else if (attributes.Properties.BlobType == BlobType.AppendBlob) + { + return new CloudAppendBlob(attributes, this.ServiceClient); + } else { throw new InvalidOperationException(SR.InvalidBlobListItem); diff --git a/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs b/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs index b1a672c45..e40f4f47b 100644 --- a/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudBlockBlob.cs @@ -21,6 +21,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Executor; using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Collections.Generic; @@ -31,6 +32,10 @@ namespace Microsoft.WindowsAzure.Storage.Blob using System.Text; using System.Threading.Tasks; +#if !PORTABLE + using Microsoft.WindowsAzure.Storage.File; +#endif + #if ASPNET_K || PORTABLE using System.Threading; #else @@ -47,9 +52,15 @@ namespace Microsoft.WindowsAzure.Storage.Blob public sealed partial class CloudBlockBlob : CloudBlob, ICloudBlob { /// - /// Opens a stream for writing to the blob. + /// Opens a stream for writing to the blob. If the blob already exists, it will be overwritten. /// /// A stream to be used for writing to the blob. + /// + /// Note that this method always makes a call to the method under the covers. + /// Set the property before calling this method to specify the block size to write, in bytes, + /// ranging from between 16 KB and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, see . + /// #if ASPNET_K || PORTABLE public Task OpenWriteAsync() #else @@ -60,12 +71,19 @@ public IAsyncOperation OpenWriteAsync() } /// - /// Opens a stream for writing to the blob. + /// Opens a stream for writing to the blob. If the blob already exists, it will be overwritten. /// /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A stream to be used for writing to the blob. + /// + /// Note that this method always makes a call to the method under the covers. + /// Set the property before calling this method to specify the block size to write, in bytes, + /// ranging from between 16 KB and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, pass in an + /// object generated using . + /// #if ASPNET_K || PORTABLE public Task OpenWriteAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -115,13 +133,20 @@ public IAsyncOperation OpenWriteAsync(AccessCondition accessCo #if ASPNET_K || PORTABLE /// - /// Opens a stream for writing to the blob. + /// Opens a stream for writing to the blob. If the blob already exists, it will be overwritten. /// /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. /// A stream to be used for writing to the blob. + /// + /// Note that this method always makes a call to the method under the covers. + /// Set the property before calling this method to specify the block size to write, in bytes, + /// ranging from between 16 KB and 4 MB inclusive. + /// To throw an exception if the blob exists instead of overwriting it, pass in an + /// object generated using . + /// public Task OpenWriteAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { this.attributes.AssertNoSnapshot(); @@ -164,11 +189,11 @@ public Task OpenWriteAsync(AccessCondition accessCondition, Blo #endif /// - /// Uploads a stream to a block blob. + /// Uploads a stream to a block blob. If the blob already exists, it will be overwritten. /// /// The stream providing the blob content. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source) #else @@ -181,12 +206,12 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source) } /// - /// Uploads a stream to a block blob. + /// Uploads a stream to a block blob. If the blob already exists, it will be overwritten. /// /// The stream providing the blob content. /// The number of bytes to write from the source stream at its current position. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source, long length) #else @@ -199,14 +224,14 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source, long length) } /// - /// Uploads a stream to a block blob. + /// Uploads a stream to a block blob. If the blob already exists, it will be overwritten. /// /// The stream providing the blob content. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) #else @@ -219,7 +244,7 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source, AccessCondition a } /// - /// Uploads a stream to a block blob. + /// Uploads a stream to a block blob. If the blob already exists, it will be overwritten. /// /// The stream providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -227,7 +252,7 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source, AccessCondition a /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) #else @@ -241,14 +266,14 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source, long length, Acce #if ASPNET_K || PORTABLE /// - /// Uploads a stream to a block blob. + /// Uploads a stream to a block blob. If the blob already exists, it will be overwritten. /// /// The stream providing the blob content. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -256,7 +281,7 @@ public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition } /// - /// Uploads a stream to a block blob. + /// Uploads a stream to a block blob. If the blob already exists, it will be overwritten. /// /// The stream providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -264,7 +289,7 @@ public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -273,7 +298,7 @@ public Task UploadFromStreamAsync(Stream source, long length, AccessCondition ac #endif /// - /// Uploads a stream to a block blob. + /// Uploads a stream to a block blob. If the blob already exists, it will be overwritten. /// /// The stream providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -281,7 +306,7 @@ public Task UploadFromStreamAsync(Stream source, long length, AccessCondition ac /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] internal Task UploadFromStreamAsyncHelper(Stream source, long? length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -297,7 +322,7 @@ internal Task UploadFromStreamAsyncHelper(Stream source, long? length, AccessCon /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] internal Task UploadFromStreamAsyncHelper(Stream source, long? length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) #else @@ -387,7 +412,7 @@ await Executor.ExecuteAsyncNullReturn( #if !PORTABLE /// - /// Uploads a file to the Windows Azure Blob Service. + /// Uploads a file to a block blob. If the blob already exists, it will be overwritten. /// #if ASPNET_K /// A string containing the file path providing the blob content. @@ -409,7 +434,7 @@ public IAsyncAction UploadFromFileAsync(StorageFile source) #endif /// - /// Uploads a file to a blob. + /// Uploads a file to a block blob. If the blob already exists, it will be overwritten. /// #if ASPNET_K /// A string containing the file path providing the blob content. @@ -417,7 +442,7 @@ public IAsyncAction UploadFromFileAsync(StorageFile source) /// An object that represents the access conditions for the blob. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -446,7 +471,7 @@ public IAsyncAction UploadFromFileAsync(StorageFile source, AccessCondition acce #if ASPNET_K /// - /// Uploads a file to a blob. + /// Uploads a file to a block blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -454,7 +479,7 @@ public IAsyncAction UploadFromFileAsync(StorageFile source, AccessCondition acce /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -472,13 +497,13 @@ public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition acce #endif /// - /// Uploads the contents of a byte array to a blob. + /// Uploads the contents of a byte array to a blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. /// The number of bytes to be written to the blob. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count) { @@ -494,7 +519,7 @@ public IAsyncAction UploadFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int #endif /// - /// Uploads the contents of a byte array to a blob. + /// Uploads the contents of a byte array to a blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -503,7 +528,7 @@ public IAsyncAction UploadFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -523,7 +548,7 @@ public IAsyncAction UploadFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int #if ASPNET_K || PORTABLE /// - /// Uploads the contents of a byte array to a blob. + /// Uploads the contents of a byte array to a blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -532,7 +557,7 @@ public IAsyncAction UploadFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -544,11 +569,11 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, Access #endif /// - /// Uploads a string of text to a blob. + /// Uploads a string of text to a blob. If the blob already exists, it will be overwritten. /// /// The text to upload, encoded as a UTF-8 string. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadTextAsync(string content) #else @@ -561,13 +586,12 @@ public IAsyncAction UploadTextAsync(string content) } /// - /// Uploads a string of text to a blob. + /// Uploads a string of text to a blob. If the blob already exists, it will be overwritten. /// /// The text to upload. /// An object that represents the access conditions for the blob. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. - /// An that represents an asynchronous action. [DoesServiceRequest] #if ASPNET_K || PORTABLE public Task UploadTextAsync(string content, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) @@ -578,36 +602,16 @@ public IAsyncAction UploadTextAsync(string content, AccessCondition accessCondit return this.UploadTextAsync(content, null /* encoding */, accessCondition, options, operationContext); } -#if ASPNET_K || PORTABLE /// - /// Uploads a string of text to a blob. + /// Uploads a string of text to a blob. If the blob already exists, it will be overwritten. /// /// The text to upload. /// A object that indicates the text encoding to use. If null, UTF-8 will be used. /// An object that represents the access conditions for the blob. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. - /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. - [DoesServiceRequest] - public Task UploadTextAsync(string content, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) - { - CommonUtility.AssertNotNull("content", content); - - byte[] contentAsBytes = Encoding.UTF8.GetBytes(content); - return this.UploadFromByteArrayAsync(contentAsBytes, 0, contentAsBytes.Length, accessCondition, options, operationContext, cancellationToken); - } -#endif - - /// - /// Downloads the contents of a blob to a stream. - /// - /// The target stream. - /// An object that represents the access conditions for the blob. If null, no condition is used. - /// A object that specifies additional options for the request. - /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadTextAsync(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -619,6 +623,7 @@ public Task UploadTextAsync(string content, Encoding encoding, AccessCondition a public IAsyncAction UploadTextAsync(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { CommonUtility.AssertNotNull("content", content); + byte[] contentAsBytes = (encoding ?? Encoding.UTF8).GetBytes(content); return this.UploadFromByteArrayAsync(contentAsBytes, 0, contentAsBytes.Length, accessCondition, options, operationContext); } @@ -626,7 +631,7 @@ public IAsyncAction UploadTextAsync(string content, Encoding encoding, AccessCon #if ASPNET_K || PORTABLE /// - /// Uploads a string of text to a blob. + /// Uploads a string of text to a blob. If the blob already exists, it will be overwritten. /// /// The text to upload, encoded as a UTF-8 string. /// A object that indicates the text encoding to use. If null, UTF-8 will be used. @@ -634,7 +639,7 @@ public IAsyncAction UploadTextAsync(string content, Encoding encoding, AccessCon /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadTextAsync(string content, Encoding encoding, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -724,7 +729,7 @@ public Task DownloadTextAsync(Encoding encoding, AccessCondition accessC } }, cancellationToken); } -#endif +#endif /// /// Creates a snapshot of the blob. @@ -789,7 +794,7 @@ public Task CreateSnapshotAsync(IDictionary meta cancellationToken), cancellationToken); } #endif - + /// /// Uploads a single block. /// @@ -798,7 +803,7 @@ public Task CreateSnapshotAsync(IDictionary meta /// An optional hash value that will be used to set the property /// on the blob. May be null or an empty string. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task PutBlockAsync(string blockId, Stream blockData, string contentMD5) #else @@ -821,7 +826,7 @@ public IAsyncAction PutBlockAsync(string blockId, IInputStream blockData, string /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task PutBlockAsync(string blockId, Stream blockData, string contentMD5, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -839,7 +844,7 @@ public Task PutBlockAsync(string blockId, Stream blockData, string contentMD5, A /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task PutBlockAsync(string blockId, Stream blockData, string contentMD5, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) #else @@ -861,35 +866,48 @@ public IAsyncAction PutBlockAsync(string blockId, IInputStream blockData, string { Stream blockDataAsStream = blockData.AsStreamForRead(); Stream seekableStream = blockDataAsStream; - if (!blockDataAsStream.CanSeek || requiresContentMD5) + bool seekableStreamCreated = false; + + try { - Stream writeToStream; - if (blockDataAsStream.CanSeek) - { - writeToStream = Stream.Null; - } - else + if (!blockDataAsStream.CanSeek || requiresContentMD5) { - seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); - writeToStream = seekableStream; - } + Stream writeToStream; + if (blockDataAsStream.CanSeek) + { + writeToStream = Stream.Null; + } + else + { + seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); + seekableStreamCreated = true; + writeToStream = seekableStream; + } - StreamDescriptor streamCopyState = new StreamDescriptor(); - long startPosition = seekableStream.Position; - await blockDataAsStream.WriteToAsync(writeToStream, null /* copyLength */, Constants.MaxBlockSize, requiresContentMD5, tempExecutionState, streamCopyState, cancellationToken); - seekableStream.Position = startPosition; + StreamDescriptor streamCopyState = new StreamDescriptor(); + long startPosition = seekableStream.Position; + await blockDataAsStream.WriteToAsync(writeToStream, null /* copyLength */, Constants.MaxBlockSize, requiresContentMD5, tempExecutionState, streamCopyState, cancellationToken); + seekableStream.Position = startPosition; - if (requiresContentMD5) + if (requiresContentMD5) + { + contentMD5 = streamCopyState.Md5; + } + } + + await Executor.ExecuteAsyncNullReturn( + this.PutBlockImpl(seekableStream, blockId, contentMD5, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken); + } + finally + { + if (seekableStreamCreated) { - contentMD5 = streamCopyState.Md5; + seekableStream.Dispose(); } } - - await Executor.ExecuteAsyncNullReturn( - this.PutBlockImpl(seekableStream, blockId, contentMD5, accessCondition, modifiedOptions), - modifiedOptions.RetryPolicy, - operationContext, - cancellationToken); #if ASPNET_K || PORTABLE }, cancellationToken); #else @@ -902,7 +920,7 @@ await Executor.ExecuteAsyncNullReturn( /// /// An enumerable collection of block IDs, as base64-encoded strings. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task PutBlockListAsync(IEnumerable blockList) #else @@ -922,7 +940,7 @@ public IAsyncAction PutBlockListAsync(IEnumerable blockList) /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task PutBlockListAsync(IEnumerable blockList, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -952,7 +970,7 @@ public IAsyncAction PutBlockListAsync(IEnumerable blockList, AccessCondi /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task PutBlockListAsync(IEnumerable blockList, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -1039,6 +1057,7 @@ public Task> DownloadBlockListAsync(BlockListingFilte /// This method fetches the blob's ETag, last modified time, and part of the copy state. /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. /// + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] #if ASPNET_K || PORTABLE public Task StartCopyFromBlobAsync(CloudBlockBlob source) @@ -1062,6 +1081,7 @@ public IAsyncOperation StartCopyFromBlobAsync(CloudBlockBlob source) /// This method fetches the blob's ETag, last modified time, and part of the copy state. /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. /// + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] #if ASPNET_K || PORTABLE public Task StartCopyFromBlobAsync(CloudBlockBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) @@ -1090,6 +1110,7 @@ public IAsyncOperation StartCopyFromBlobAsync(CloudBlockBlob source, Acc /// This method fetches the blob's ETag, last modified time, and part of the copy state. /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. /// + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(CloudBlockBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -1115,7 +1136,32 @@ public IAsyncOperation StartCopyAsync(CloudBlockBlob source) { return this.StartCopyAsync(CloudBlob.SourceBlobToUri(source)); } - + +#if !PORTABLE + /// + /// Begins an operation to start copying a file's contents, properties, and metadata to a new blob. + /// + /// The source file. + /// An object that represents the access conditions for the source file. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the blob's ETag, last modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] +#if ASPNET_K + public Task StartCopyAsync(CloudFile source) +#else + public IAsyncOperation StartCopyAsync(CloudFile source) +#endif + { + return this.StartCopyAsync(CloudFile.SourceFileToUri(source)); + } +#endif + /// /// Begins an operation to start copying another block blob's contents, properties, and metadata to a new blob. /// @@ -1163,6 +1209,55 @@ public Task StartCopyAsync(CloudBlockBlob source, AccessCondition source } #endif +#if !PORTABLE + /// + /// Begins an operation to start copying a file's contents, properties, and metadata to a new blob. + /// + /// The source file. + /// An object that represents the access conditions for the source file. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the blob's ETag, last modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// +#if ASPNET_K + public Task StartCopyAsync(CloudFile source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.StartCopyAsync(source, sourceAccessCondition, destAccessCondition, options, operationContext, CancellationToken.None); + } +#else + public IAsyncOperation StartCopyAsync(CloudFile source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) + { + return this.StartCopyAsync(CloudFile.SourceFileToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext); + } +#endif + +#if ASPNET_K || PORTABLE + /// + /// Begins an operation to start copying a file's contents, properties, and metadata to a new blob. + /// + /// The source file. + /// An object that represents the access conditions for the source file. If null, no condition is used. + /// An object that represents the access conditions for the destination blob. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the blob's ETag, last modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] + public Task StartCopyAsync(CloudFile source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + return this.StartCopyAsync(CloudFile.SourceFileToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext, cancellationToken); + } +#endif +#endif + /// /// Implementation for the CreateSnapshot method. /// @@ -1293,6 +1388,7 @@ internal RESTCommand PutBlockListImpl(IEnumerable bl putCmd.Handler = this.ServiceClient.AuthenticationHandler; putCmd.BuildClient = HttpClientFactory.BuildHttpClient; putCmd.BuildContent = (cmd, ctx) => HttpContentFactory.BuildContentFromStream(memoryStream, 0, memoryStream.Length, contentMD5, cmd, ctx); + putCmd.StreamToDispose = memoryStream; putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => { HttpRequestMessage msg = BlobHttpRequestMessageFactory.PutBlockList(uri, serverTimeout, this.Properties, accessCondition, cnt, ctx); diff --git a/Lib/WindowsRuntime/Blob/CloudPageBlob.cs b/Lib/WindowsRuntime/Blob/CloudPageBlob.cs index cd7a5117d..091380d7d 100644 --- a/Lib/WindowsRuntime/Blob/CloudPageBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudPageBlob.cs @@ -44,10 +44,13 @@ namespace Microsoft.WindowsAzure.Storage.Blob public sealed partial class CloudPageBlob : CloudBlob, ICloudBlob { /// - /// Opens a stream for writing to the blob. + /// Opens a stream for writing to the blob. If the blob already exists, then existing data in the blob may be overwritten. /// - /// The size of the write operation, in bytes. The size must be a multiple of 512. + /// The size of the page blob, in bytes. The size must be a multiple of 512. If null, the page blob must already exist. /// A stream to be used for writing to the blob. + /// + /// To avoid overwriting and instead throw an error, see . + /// [DoesServiceRequest] #if ASPNET_K || PORTABLE public Task OpenWriteAsync(long? size) @@ -59,13 +62,17 @@ public IAsyncOperation OpenWriteAsync(long? size) } /// - /// Opens a stream for writing to the blob. + /// Opens a stream for writing to the blob. If the blob already exists, then existing data in the blob may be overwritten. /// - /// The size of the write operation, in bytes. The size must be a multiple of 512. + /// The size of the page blob, in bytes. The size must be a multiple of 512. If null, the page blob must already exist. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A stream to be used for writing to the blob. + /// + /// To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . + /// [DoesServiceRequest] #if ASPNET_K || PORTABLE public Task OpenWriteAsync(long? size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) @@ -76,12 +83,16 @@ public Task OpenWriteAsync(long? size, AccessCondition accessCo /// /// Opens a stream for writing to the blob. /// - /// The size of the write operation, in bytes. The size must be a multiple of 512. + /// The size of the page blob, in bytes. The size must be a multiple of 512. If null, the page blob must already exist. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. /// A stream to be used for writing to the blob. + /// + /// To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . + /// [DoesServiceRequest] public Task OpenWriteAsync(long? size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) #else @@ -139,11 +150,11 @@ public IAsyncOperation OpenWriteAsync(long? size, AccessCondit } /// - /// Uploads a stream to a page blob. + /// Uploads a stream to a page blob. If the blob already exists, it will be overwritten. /// /// The stream providing the blob content. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source) #else @@ -156,12 +167,12 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source) } /// - /// Uploads a stream to a page blob. + /// Uploads a stream to a page blob. If the blob already exists, it will be overwritten. /// /// The stream providing the blob content. /// The number of bytes to write from the source stream at its current position. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source, long length) #else @@ -174,14 +185,14 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source, long length) } /// - /// Uploads a stream to a page blob. + /// Uploads a stream to a page blob. If the blob already exists, it will be overwritten. /// /// The stream providing the blob content. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) #else @@ -194,7 +205,7 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source, AccessCondition a } /// - /// Uploads a stream to a page blob. + /// Uploads a stream to a page blob. If the blob already exists, it will be overwritten. /// /// The stream providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -202,7 +213,7 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source, AccessCondition a /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) #else @@ -216,14 +227,14 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source, long length, Acce #if ASPNET_K || PORTABLE /// - /// Uploads a stream to a page blob. + /// Uploads a stream to a page blob. If the blob already exists, it will be overwritten. /// /// The stream providing the blob content. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -231,7 +242,7 @@ public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition } /// - /// Uploads a stream to a page blob. + /// Uploads a stream to a page blob. If the blob already exists, it will be overwritten. /// /// The stream providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -239,7 +250,7 @@ public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -248,7 +259,7 @@ public Task UploadFromStreamAsync(Stream source, long length, AccessCondition ac #endif /// - /// Uploads a stream to a page blob. + /// Uploads a stream to a page blob. If the blob already exists, it will be overwritten. /// /// The stream providing the blob content. /// The number of bytes to write from the source stream at its current position. @@ -256,7 +267,7 @@ public Task UploadFromStreamAsync(Stream source, long length, AccessCondition ac /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] internal Task UploadFromStreamAsyncHelper(Stream source, long? length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -272,7 +283,7 @@ internal Task UploadFromStreamAsyncHelper(Stream source, long? length, AccessCon /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] internal Task UploadFromStreamAsyncHelper(Stream source, long? length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) #else @@ -335,12 +346,12 @@ internal IAsyncAction UploadFromStreamAsyncHelper(IInputStream source, long? len #if !PORTABLE /// - /// Uploads a file to the Windows Azure Blob Service. + /// Uploads a file to a page blob. If the blob already exists, it will be overwritten. /// #if ASPNET_K /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromFileAsync(string path, FileMode mode) { @@ -357,7 +368,7 @@ public IAsyncAction UploadFromFileAsync(StorageFile source) #endif /// - /// Uploads a file to a blob. + /// Uploads a file to a page blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -365,7 +376,7 @@ public IAsyncAction UploadFromFileAsync(StorageFile source) /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -390,7 +401,7 @@ public IAsyncAction UploadFromFileAsync(StorageFile source, AccessCondition acce #if ASPNET_K /// - /// Uploads a file to a blob. + /// Uploads a file to a page blob. If the blob already exists, it will be overwritten. /// /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. @@ -398,7 +409,7 @@ public IAsyncAction UploadFromFileAsync(StorageFile source, AccessCondition acce /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -416,13 +427,13 @@ public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition acce #endif /// - /// Uploads the contents of a byte array to a blob. + /// Uploads the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. /// The number of bytes to be written to the blob. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count) #else @@ -435,7 +446,7 @@ public IAsyncAction UploadFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int } /// - /// Uploads the contents of a byte array to a blob. + /// Uploads the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -444,7 +455,7 @@ public IAsyncAction UploadFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -464,7 +475,7 @@ public IAsyncAction UploadFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int #if ASPNET_K || PORTABLE /// - /// Uploads the contents of a byte array to a blob. + /// Uploads the contents of a byte array to a page blob. If the blob already exists, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. @@ -473,7 +484,7 @@ public IAsyncAction UploadFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -485,11 +496,12 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, Access #endif /// - /// Creates a page blob. + /// Creates a page blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, + /// use . /// /// The maximum size of the page blob, in bytes. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync(long size) #else @@ -502,14 +514,15 @@ public IAsyncAction CreateAsync(long size) } /// - /// Creates a page blob. + /// Creates a page blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . /// /// The maximum size of the page blob, in bytes. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync(long size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -531,14 +544,15 @@ public IAsyncAction CreateAsync(long size, AccessCondition accessCondition, Blob #if ASPNET_K || PORTABLE /// - /// Creates a page blob. + /// Creates a page blob. If the blob already exists, this operation will overwrite it. To throw an exception if the blob exists, instead of overwriting, pass in an + /// object generated using . /// /// The maximum size of the page blob, in bytes. /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync(long size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -551,12 +565,13 @@ public Task CreateAsync(long size, AccessCondition accessCondition, BlobRequestO } #endif +#if !PORTABLE /// /// Resizes the page blob to the specified size. /// /// The maximum size of the page blob, in bytes. -#if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. +#if ASPNET_K + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ResizeAsync(long size) #else @@ -575,8 +590,8 @@ public IAsyncAction ResizeAsync(long size) /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. -#if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. +#if ASPNET_K + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ResizeAsync(long size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -596,7 +611,7 @@ public IAsyncAction ResizeAsync(long size, AccessCondition accessCondition, Blob } #endif -#if ASPNET_K || PORTABLE +#if ASPNET_K /// /// Resizes the page blob to the specified size. /// @@ -605,7 +620,7 @@ public IAsyncAction ResizeAsync(long size, AccessCondition accessCondition, Blob /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ResizeAsync(long size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -616,6 +631,7 @@ public Task ResizeAsync(long size, AccessCondition accessCondition, BlobRequestO operationContext, cancellationToken), cancellationToken); } +#endif #endif /// @@ -624,7 +640,7 @@ public Task ResizeAsync(long size, AccessCondition accessCondition, BlobRequestO /// A value of type , indicating the operation to perform on the sequence number. /// The sequence number. Set this parameter to null if is equal to . #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetSequenceNumberAsync(SequenceNumberAction sequenceNumberAction, long? sequenceNumber) #else @@ -645,7 +661,7 @@ public IAsyncAction SetSequenceNumberAsync(SequenceNumberAction sequenceNumberAc /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetSequenceNumberAsync(SequenceNumberAction sequenceNumberAction, long? sequenceNumber, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -824,7 +840,7 @@ public Task CreateSnapshotAsync(IDictionary metad /// An optional hash value that will be used to set the property /// on the blob. May be null or an empty string. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task WritePagesAsync(Stream pageData, long startOffset, string contentMD5) #else @@ -847,7 +863,7 @@ public IAsyncAction WritePagesAsync(IInputStream pageData, long startOffset, str /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task WritePagesAsync(Stream pageData, long startOffset, string contentMD5, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -865,7 +881,7 @@ public Task WritePagesAsync(Stream pageData, long startOffset, string contentMD5 /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task WritePagesAsync(Stream pageData, long startOffset, string contentMD5, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) #else @@ -886,35 +902,48 @@ public IAsyncAction WritePagesAsync(IInputStream pageData, long startOffset, str { Stream pageDataAsStream = pageData.AsStreamForRead(); Stream seekableStream = pageDataAsStream; - if (!pageDataAsStream.CanSeek || requiresContentMD5) + bool seekableStreamCreated = false; + + try { - Stream writeToStream; - if (pageDataAsStream.CanSeek) - { - writeToStream = Stream.Null; - } - else + if (!pageDataAsStream.CanSeek || requiresContentMD5) { - seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); - writeToStream = seekableStream; + Stream writeToStream; + if (pageDataAsStream.CanSeek) + { + writeToStream = Stream.Null; + } + else + { + seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); + seekableStreamCreated = true; + writeToStream = seekableStream; + } + + StreamDescriptor streamCopyState = new StreamDescriptor(); + long startPosition = seekableStream.Position; + await pageDataAsStream.WriteToAsync(writeToStream, null /* copyLength */, Constants.MaxBlockSize, requiresContentMD5, tempExecutionState, streamCopyState, cancellationToken); + seekableStream.Position = startPosition; + + if (requiresContentMD5) + { + contentMD5 = streamCopyState.Md5; + } } - StreamDescriptor streamCopyState = new StreamDescriptor(); - long startPosition = seekableStream.Position; - await pageDataAsStream.WriteToAsync(writeToStream, null /* copyLength */, Constants.MaxBlockSize, requiresContentMD5, tempExecutionState, streamCopyState, cancellationToken); - seekableStream.Position = startPosition; - - if (requiresContentMD5) + await Executor.ExecuteAsyncNullReturn( + this.PutPageImpl(seekableStream, startOffset, contentMD5, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken); + } + finally + { + if (seekableStreamCreated) { - contentMD5 = streamCopyState.Md5; + seekableStream.Dispose(); } } - - await Executor.ExecuteAsyncNullReturn( - this.PutPageImpl(seekableStream, startOffset, contentMD5, accessCondition, modifiedOptions), - modifiedOptions.RetryPolicy, - operationContext, - cancellationToken); #if ASPNET_K || PORTABLE }, cancellationToken); #else @@ -928,7 +957,7 @@ await Executor.ExecuteAsyncNullReturn( /// The offset at which to begin clearing pages, in bytes. The offset must be a multiple of 512. /// The length of the data range to be cleared, in bytes. The length must be a multiple of 512. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ClearPagesAsync(long startOffset, long length) #else @@ -949,7 +978,7 @@ public IAsyncAction ClearPagesAsync(long startOffset, long length) /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ClearPagesAsync(long startOffset, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { @@ -979,7 +1008,7 @@ public IAsyncAction ClearPagesAsync(long startOffset, long length, AccessConditi /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ClearPagesAsync(long startOffset, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -1001,6 +1030,7 @@ public Task ClearPagesAsync(long startOffset, long length, AccessCondition acces /// This method fetches the blob's ETag, last modified time, and part of the copy state. /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. /// + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] #if ASPNET_K || PORTABLE public Task StartCopyFromBlobAsync(CloudPageBlob source) @@ -1024,6 +1054,7 @@ public IAsyncOperation StartCopyFromBlobAsync(CloudPageBlob source) /// This method fetches the blob's ETag, last modified time, and part of the copy state. /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. /// + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] #if ASPNET_K || PORTABLE public Task StartCopyFromBlobAsync(CloudPageBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext) @@ -1052,6 +1083,7 @@ public IAsyncOperation StartCopyFromBlobAsync(CloudPageBlob source, Acce /// This method fetches the blob's ETag, last modified time, and part of the copy state. /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. /// + [Obsolete("Deprecated this method in favor of StartCopyAsync.")] [DoesServiceRequest] public Task StartCopyFromBlobAsync(CloudPageBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, BlobRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -1086,7 +1118,6 @@ public IAsyncOperation StartCopyAsync(CloudPageBlob source) /// An object that represents the access conditions for the destination blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. - /// An that represents an asynchronous action. /// The copy ID associated with the copy operation. /// /// This method fetches the blob's ETag, last modified time, and part of the copy state. diff --git a/Lib/WindowsRuntime/Blob/ICloudBlob.cs b/Lib/WindowsRuntime/Blob/ICloudBlob.cs index 4a66b92b1..889aaac09 100644 --- a/Lib/WindowsRuntime/Blob/ICloudBlob.cs +++ b/Lib/WindowsRuntime/Blob/ICloudBlob.cs @@ -55,7 +55,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// The stream providing the blob content. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task UploadFromStreamAsync(Stream source); #else /// An that represents an asynchronous action. @@ -70,7 +70,7 @@ public partial interface ICloudBlob : IListBlobItem /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext); #else /// An that represents an asynchronous action. @@ -83,7 +83,7 @@ public partial interface ICloudBlob : IListBlobItem /// The stream providing the blob content. /// The number of bytes to write from the source stream at its current position. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task UploadFromStreamAsync(Stream source, long length); #else /// An that represents an asynchronous action. @@ -99,7 +99,7 @@ public partial interface ICloudBlob : IListBlobItem /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task UploadFromStreamAsync(Stream source, long length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext); #else /// An that represents an asynchronous action. @@ -112,7 +112,7 @@ public partial interface ICloudBlob : IListBlobItem #if ASPNET_K /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task UploadFromFileAsync(string path, FileMode mode); #else /// The file providing the blob content. @@ -133,7 +133,7 @@ public partial interface ICloudBlob : IListBlobItem /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task UploadFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext); #else /// An that represents an asynchronous action. @@ -148,7 +148,7 @@ public partial interface ICloudBlob : IListBlobItem /// The zero-based byte offset in buffer at which to begin uploading bytes to the blob. /// The number of bytes to be written to the blob. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task UploadFromByteArrayAsync(byte[] buffer, int index, int count); #else /// An that represents an asynchronous action. @@ -165,7 +165,7 @@ public partial interface ICloudBlob : IListBlobItem /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext); #else /// An that represents an asynchronous action. @@ -177,7 +177,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// The target stream. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task DownloadToStreamAsync(Stream target); #else /// An that represents an asynchronous action. @@ -192,7 +192,7 @@ public partial interface ICloudBlob : IListBlobItem /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task DownloadToStreamAsync(Stream target, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext); #else /// An that represents an asynchronous action. @@ -206,7 +206,7 @@ public partial interface ICloudBlob : IListBlobItem #if ASPNET_K /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task DownloadToFileAsync(string path, FileMode mode); #else /// The target file. @@ -227,7 +227,7 @@ public partial interface ICloudBlob : IListBlobItem /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task DownloadToFileAsync(string path, FileMode mode, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext); #else /// An that represents an asynchronous action. @@ -269,7 +269,7 @@ public partial interface ICloudBlob : IListBlobItem /// The starting offset of the data range, in bytes. /// The length of the data range, in bytes. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task DownloadRangeToStreamAsync(Stream target, long? offset, long? length); #else /// An that represents an asynchronous action. @@ -286,7 +286,7 @@ public partial interface ICloudBlob : IListBlobItem /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task DownloadRangeToStreamAsync(Stream target, long? offset, long? length, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext); #else /// An that represents an asynchronous action. @@ -350,7 +350,7 @@ public partial interface ICloudBlob : IListBlobItem /// Populates a blob's properties and metadata. /// #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task FetchAttributesAsync(); #else /// An that represents an asynchronous action. @@ -364,7 +364,7 @@ public partial interface ICloudBlob : IListBlobItem /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task FetchAttributesAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext); #else /// An that represents an asynchronous action. @@ -375,7 +375,7 @@ public partial interface ICloudBlob : IListBlobItem /// Updates the blob's metadata. /// #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task SetMetadataAsync(); #else /// An that represents an asynchronous action. @@ -389,7 +389,7 @@ public partial interface ICloudBlob : IListBlobItem /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task SetMetadataAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext); #else /// An that represents an asynchronous action. @@ -400,7 +400,7 @@ public partial interface ICloudBlob : IListBlobItem /// Updates the blob's properties. /// #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task SetPropertiesAsync(); #else /// An that represents an asynchronous action. @@ -414,7 +414,7 @@ public partial interface ICloudBlob : IListBlobItem /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task SetPropertiesAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext); #else /// An that represents an asynchronous action. @@ -425,7 +425,7 @@ public partial interface ICloudBlob : IListBlobItem /// Deletes the blob. /// #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task DeleteAsync(); #else /// An that represents an asynchronous action. @@ -440,7 +440,7 @@ public partial interface ICloudBlob : IListBlobItem /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task DeleteAsync(DeleteSnapshotsOption deleteSnapshotsOption, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext); #else /// An that represents an asynchronous action. @@ -505,7 +505,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// An object that represents the access conditions for the blob, including a required lease ID. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task RenewLeaseAsync(AccessCondition accessCondition); #else /// An that represents an asynchronous action. @@ -519,7 +519,7 @@ public partial interface ICloudBlob : IListBlobItem /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task RenewLeaseAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext); #else /// An that represents an asynchronous action. @@ -557,7 +557,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// An object that represents the access conditions for the blob, including a required lease ID. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task ReleaseLeaseAsync(AccessCondition accessCondition); #else /// An that represents an asynchronous action. @@ -571,7 +571,7 @@ public partial interface ICloudBlob : IListBlobItem /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task ReleaseLeaseAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext); #else /// An that represents an asynchronous action. @@ -610,7 +610,7 @@ public partial interface ICloudBlob : IListBlobItem /// /// A string identifying the copy operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task AbortCopyAsync(string copyId); #else /// An that represents an asynchronous action. @@ -625,7 +625,7 @@ public partial interface ICloudBlob : IListBlobItem /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. Task AbortCopyAsync(string copyId, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext); #else /// An that represents an asynchronous action. diff --git a/Lib/WindowsRuntime/Blob/ICloudBlobStream.cs b/Lib/WindowsRuntime/Blob/ICloudBlobStream.cs index d67b138b3..aae467e31 100644 --- a/Lib/WindowsRuntime/Blob/ICloudBlobStream.cs +++ b/Lib/WindowsRuntime/Blob/ICloudBlobStream.cs @@ -29,7 +29,7 @@ public abstract class CloudBlobStream : Stream /// /// Asynchronously clears all buffers for this stream, causes any buffered data to be written to the underlying blob, and commits the blob. /// - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. public abstract Task CommitAsync(); } #else diff --git a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs index de8d3adf7..3aa385d7a 100644 --- a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs +++ b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpRequestMessageFactory.cs @@ -28,6 +28,26 @@ namespace Microsoft.WindowsAzure.Storage.Blob.Protocol internal static class BlobHttpRequestMessageFactory { + /// + /// Constructs a web request to commit a block to an append blob. + /// + /// A specifying the absolute URI to the blob. + /// An integer specifying the server timeout interval. + /// An object that represents the condition that must be met in order for the request to proceed. + /// The HTTP entity body and content headers. + /// An object that represents the context for the current operation. + /// A object. + public static HttpRequestMessage AppendBlock(Uri uri, int? timeout, AccessCondition accessCondition, HttpContent content, OperationContext operationContext) + { + UriQueryBuilder builder = new UriQueryBuilder(); + builder.Add(Constants.QueryConstants.Component, "appendblock"); + + HttpRequestMessage request = HttpRequestMessageFactory.CreateRequestMessage(HttpMethod.Put, uri, timeout, builder, content, operationContext); + request.ApplyAccessCondition(accessCondition); + request.ApplyAppendCondition(accessCondition); + return request; + } + /// /// Constructs a web request to create a new block blob or page blob, or to update the content /// of an existing block blob. @@ -88,10 +108,14 @@ public static HttpRequestMessage Put(Uri uri, int? timeout, BlobProperties prope request.Headers.Add(Constants.HeaderConstants.BlobContentLengthHeader, pageBlobSize.ToString(NumberFormatInfo.InvariantInfo)); properties.Length = pageBlobSize; } - else + else if (blobType == BlobType.BlockBlob) { request.Headers.Add(Constants.HeaderConstants.BlobType, Constants.HeaderConstants.BlockBlob); } + else + { + request.Headers.Add(Constants.HeaderConstants.BlobType, Constants.HeaderConstants.AppendBlob); + } request.ApplyAccessCondition(accessCondition); return request; @@ -106,7 +130,7 @@ private static void AddSnapshot(UriQueryBuilder builder, DateTimeOffset? snapsho { if (snapshot.HasValue) { - builder.Add("snapshot", BlobRequest.ConvertDateTimeToSnapshotString(snapshot.Value)); + builder.Add("snapshot", Request.ConvertDateTimeToSnapshotString(snapshot.Value)); } } @@ -607,7 +631,7 @@ public static HttpRequestMessage Get(Uri uri, int? timeout, DateTimeOffset? snap UriQueryBuilder builder = new UriQueryBuilder(); if (snapshot.HasValue) { - builder.Add("snapshot", BlobRequest.ConvertDateTimeToSnapshotString(snapshot.Value)); + builder.Add("snapshot", Request.ConvertDateTimeToSnapshotString(snapshot.Value)); } HttpRequestMessage request = HttpRequestMessageFactory.CreateRequestMessage(HttpMethod.Get, uri, timeout, builder, content, operationContext); diff --git a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs index 19c50c376..c4e0bbef7 100644 --- a/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs +++ b/Lib/WindowsRuntime/Blob/Protocol/BlobHttpResponseParsers.cs @@ -21,6 +21,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob.Protocol using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Collections.Generic; + using System.Globalization; using System.Net.Http; #if ASPNET_K || PORTABLE @@ -103,7 +104,14 @@ public static BlobProperties GetProperties(HttpResponseMessage response) string sequenceNumber = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.BlobSequenceNumber); if (!string.IsNullOrEmpty(sequenceNumber)) { - properties.PageBlobSequenceNumber = long.Parse(sequenceNumber); + properties.PageBlobSequenceNumber = long.Parse(sequenceNumber, CultureInfo.InvariantCulture); + } + + // Get committed block count + string comittedBlockCount = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.BlobCommittedBlockCount); + if (!string.IsNullOrEmpty(comittedBlockCount)) + { + properties.AppendBlobCommittedBlockCount = int.Parse(comittedBlockCount, CultureInfo.InvariantCulture); } return properties; diff --git a/Lib/WindowsRuntime/Core/Auth/SharedKeyCanonicalizer.cs b/Lib/WindowsRuntime/Core/Auth/SharedKeyCanonicalizer.cs index 0bae75c54..6dfb3e453 100644 --- a/Lib/WindowsRuntime/Core/Auth/SharedKeyCanonicalizer.cs +++ b/Lib/WindowsRuntime/Core/Auth/SharedKeyCanonicalizer.cs @@ -67,37 +67,7 @@ public string CanonicalizeHttpRequest(HttpRequestMessage request, string account { canonicalizedString.AppendCanonicalizedElement(null); canonicalizedString.AppendCanonicalizedElement(null); - -#if WINDOWS_PHONE - // Always add 0 for content length header for all requests except GET as Wininet on windows phone 8.1 has that behaviour - if (request.Method != HttpMethod.Get) - { - canonicalizedString.AppendCanonicalizedElement("0"); - } - else - { - canonicalizedString.AppendCanonicalizedElement(null); - } -#elif ASPNET_K && ASPNETCORE50 - if (request.Method == HttpMethod.Put) - { - canonicalizedString.AppendCanonicalizedElement("0"); - } - else - { - canonicalizedString.AppendCanonicalizedElement(null); - } -#else - if (request.Method == HttpMethod.Put || request.Method == HttpMethod.Delete) - { - canonicalizedString.AppendCanonicalizedElement("0"); - } - else - { - canonicalizedString.AppendCanonicalizedElement(null); - } -#endif - + canonicalizedString.AppendCanonicalizedElement(null); canonicalizedString.AppendCanonicalizedElement(null); canonicalizedString.AppendCanonicalizedElement(null); } diff --git a/Lib/WindowsRuntime/File/CloudFile.cs b/Lib/WindowsRuntime/File/CloudFile.cs index bd446cb6f..9548ea26b 100644 --- a/Lib/WindowsRuntime/File/CloudFile.cs +++ b/Lib/WindowsRuntime/File/CloudFile.cs @@ -17,6 +17,7 @@ namespace Microsoft.WindowsAzure.Storage.File { + using Microsoft.WindowsAzure.Storage.Blob; using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Executor; using Microsoft.WindowsAzure.Storage.Core.Util; @@ -35,6 +36,7 @@ namespace Microsoft.WindowsAzure.Storage.File #else using System.Runtime.InteropServices.WindowsRuntime; using Windows.Foundation; + using Windows.Foundation.Metadata; using Windows.Storage; using Windows.Storage.Streams; #endif @@ -106,7 +108,7 @@ public Task OpenReadAsync(AccessCondition accessCondition, FileRequestOp #endif /// - /// Opens a stream for writing to the file. + /// Opens a stream for writing to the file. If the file already exists, then existing data in the file may be overwritten. /// /// The size of the write operation, in bytes. /// A stream to be used for writing to the file. @@ -121,7 +123,7 @@ public IAsyncOperation OpenWriteAsync(long? size) } /// - /// Opens a stream for writing to the file. + /// Opens a stream for writing to the file. If the file already exists, then existing data in the file may be overwritten. /// /// The size of the write operation, in bytes. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -199,7 +201,7 @@ public IAsyncOperation OpenWriteAsync(long? size, AccessCondit } /// - /// Uploads a stream to a file. + /// Uploads a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// An that represents an asynchronous action. @@ -214,12 +216,12 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source) } /// - /// Uploads a stream to a file. + /// Uploads a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// The number of bytes to write from the source stream at its current position. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source, long length) #else @@ -232,7 +234,7 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source, long length) } /// - /// Uploads a stream to a file. + /// Uploads a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -252,7 +254,7 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source, AccessCondition a } /// - /// Uploads a stream to a file. + /// Uploads a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// The number of bytes to write from the source stream at its current position. @@ -260,7 +262,7 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source, AccessCondition a /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source, long length, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) #else @@ -281,7 +283,7 @@ public IAsyncAction UploadFromStreamAsync(IInputStream source, long length, Acce /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -297,7 +299,7 @@ public Task UploadFromStreamAsync(Stream source, AccessCondition accessCondition /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromStreamAsync(Stream source, long length, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -306,7 +308,7 @@ public Task UploadFromStreamAsync(Stream source, long length, AccessCondition ac #endif /// - /// Uploads a stream to a file. + /// Uploads a stream to a file. If the file already exists on the service, it will be overwritten. /// /// The stream providing the file content. /// The number of bytes to write from the source stream at its current position. @@ -314,7 +316,7 @@ public Task UploadFromStreamAsync(Stream source, long length, AccessCondition ac /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] internal Task UploadFromStreamAsyncHelper(Stream source, long? length, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { @@ -330,7 +332,7 @@ internal Task UploadFromStreamAsyncHelper(Stream source, long? length, AccessCon /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] internal Task UploadFromStreamAsyncHelper(Stream source, long? length, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) #else @@ -386,12 +388,12 @@ internal IAsyncAction UploadFromStreamAsyncHelper(IInputStream source, long? len } /// - /// Uploads a file to the Windows Azure File Service. + /// Uploads a file to the Azure File Service. If the file already exists on the service, it will be overwritten. /// #if ASPNET_K /// A string containing the path to the target file. /// A enumeration value that determines how to open or create the file. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromFileAsync(string path, FileMode mode) { @@ -408,7 +410,7 @@ public IAsyncAction UploadFromFileAsync(StorageFile source) #endif /// - /// Uploads a file to a file. + /// Uploads a file to the Azure File Service. If the file already exists on the service, it will be overwritten. /// #if ASPNET_K /// A string containing the path to the target file. @@ -416,7 +418,7 @@ public IAsyncAction UploadFromFileAsync(StorageFile source) /// An object that represents the access conditions for the file. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { @@ -445,7 +447,7 @@ public IAsyncAction UploadFromFileAsync(StorageFile source, AccessCondition acce #if ASPNET_K /// - /// Uploads a file to a file. + /// Uploads a file to the Azure File Service. If the file already exists on the service, it will be overwritten. /// /// A string containing the path to the target file. /// A enumeration value that determines how to open or create the file. @@ -453,7 +455,7 @@ public IAsyncAction UploadFromFileAsync(StorageFile source, AccessCondition acce /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -470,13 +472,13 @@ public Task UploadFromFileAsync(string path, FileMode mode, AccessCondition acce #endif /// - /// Uploads the contents of a byte array to a file. + /// Uploads the contents of a byte array to a file. If the file already exists on the service, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the file. /// The number of bytes to be written to the file. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count) #else @@ -489,7 +491,7 @@ public IAsyncAction UploadFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int } /// - /// Uploads the contents of a byte array to a file. + /// Uploads the contents of a byte array to a file. If the file already exists on the service, it will be overwritten. /// /// An array of bytes. /// The zero-based byte offset in buffer at which to begin uploading bytes to the file. @@ -498,7 +500,7 @@ public IAsyncAction UploadFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { @@ -527,7 +529,7 @@ public IAsyncAction UploadFromByteArrayAsync([ReadOnlyArray] byte[] buffer, int /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -539,11 +541,11 @@ public Task UploadFromByteArrayAsync(byte[] buffer, int index, int count, Access #endif /// - /// Uploads a string of text to a file. + /// Uploads a string of text to a file. If the file already exists on the service, it will be overwritten. /// /// The text to upload, encoded as a UTF-8 string. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadTextAsync(string content) #else @@ -556,14 +558,14 @@ public IAsyncAction UploadTextAsync(string content) } /// - /// Uploads a string of text to a file. + /// Uploads a string of text to a file. If the file already exists on the service, it will be overwritten. /// /// The text to upload, encoded as a UTF-8 string. /// An object that represents the access conditions for the file. /// An object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadTextAsync(string content, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { @@ -581,17 +583,16 @@ public IAsyncAction UploadTextAsync(string content, AccessCondition accessCondit } #endif - #if ASPNET_K /// - /// Uploads a string of text to a file. + /// Uploads a string of text to a file. If the file already exists on the service, it will be overwritten. /// /// The text to upload, encoded as a UTF-8 string. /// An object that represents the access conditions for the file. /// An object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UploadTextAsync(string content, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -607,7 +608,7 @@ public Task UploadTextAsync(string content, AccessCondition accessCondition, Fil /// /// The target stream. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadToStreamAsync(Stream target) #else @@ -627,7 +628,7 @@ public IAsyncAction DownloadToStreamAsync(IOutputStream target) /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadToStreamAsync(Stream target, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { @@ -642,7 +643,6 @@ public IAsyncAction DownloadToStreamAsync(IOutputStream target, AccessCondition } #endif - #if ASPNET_K /// /// Downloads the contents of a file to a stream. @@ -652,7 +652,7 @@ public IAsyncAction DownloadToStreamAsync(IOutputStream target, AccessCondition /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadToStreamAsync(Stream target, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -666,7 +666,7 @@ public Task DownloadToStreamAsync(Stream target, AccessCondition accessCondition #if ASPNET_K /// A string containing the file path providing the blob content. /// A enumeration value that specifies how to open the file. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadToFileAsync(string path, FileMode mode) { @@ -691,7 +691,7 @@ public IAsyncAction DownloadToFileAsync(StorageFile target) /// An object that represents the access conditions for the file. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadToFileAsync(string path, FileMode mode, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { @@ -729,7 +729,7 @@ public IAsyncAction DownloadToFileAsync(StorageFile target, AccessCondition acce /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadToFileAsync(string path, FileMode mode, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -808,7 +808,7 @@ public Task DownloadToByteArrayAsync(byte[] target, int index, AccessCondit /// The offset at which to begin downloading the file, in bytes. /// The length of the data to download from the file, in bytes. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadRangeToStreamAsync(Stream target, long? offset, long? length) #else @@ -893,7 +893,7 @@ public Task DownloadTextAsync(AccessCondition accessCondition, FileReque /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadRangeToStreamAsync(Stream target, long? offset, long? length, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { @@ -929,7 +929,7 @@ public IAsyncAction DownloadRangeToStreamAsync(IOutputStream target, long? offse /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DownloadRangeToStreamAsync(Stream target, long? offset, long? length, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -1024,7 +1024,7 @@ public Task DownloadRangeToByteArrayAsync(byte[] target, int index, long? f #endif /// - /// Creates a file. + /// Creates a file. If the file already exists, it will be overwritten. /// /// The maximum size of the file, in bytes. [DoesServiceRequest] @@ -1038,7 +1038,7 @@ public IAsyncAction CreateAsync(long size) } /// - /// Creates a file. + /// Creates a file. If the file already exists, it will be overwritten. /// /// The maximum size of the file, in bytes. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -1064,7 +1064,7 @@ public IAsyncAction CreateAsync(long size, AccessCondition accessCondition, File #if ASPNET_K /// - /// Creates a file. + /// Creates a file. If the file already exists, it will be overwritten. /// /// The maximum size of the file, in bytes. /// An object that represents the access conditions for the file. If null, no condition is used. @@ -1596,7 +1596,7 @@ public Task SetMetadataAsync(AccessCondition accessCondition, FileRequestOptions /// An optional hash value that will be used to set the property /// on the file. May be null or an empty string. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task WriteRangeAsync(Stream rangeData, long startOffset, string contentMD5) #else @@ -1619,7 +1619,7 @@ public IAsyncAction WriteRangeAsync(IInputStream rangeData, long startOffset, st /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task WriteRangeAsync(Stream rangeData, long startOffset, string contentMD5, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) { @@ -1637,7 +1637,7 @@ public Task WriteRangeAsync(Stream rangeData, long startOffset, string contentMD /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. public Task WriteRangeAsync(Stream rangeData, long startOffset, string contentMD5, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) #else /// An that represents an asynchronous action. @@ -1662,40 +1662,53 @@ public IAsyncAction WriteRangeAsync(IInputStream rangeData, long startOffset, st Stream rangeDataAsStream = rangeData.AsStreamForRead(); Stream seekableStream = rangeDataAsStream; - if (!rangeDataAsStream.CanSeek || requiresContentMD5) + bool seekableStreamCreated = false; + + try { - Stream writeToStream; - if (rangeDataAsStream.CanSeek) - { - writeToStream = Stream.Null; - } - else + if (!rangeDataAsStream.CanSeek || requiresContentMD5) { - seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); - writeToStream = seekableStream; - } + Stream writeToStream; + if (rangeDataAsStream.CanSeek) + { + writeToStream = Stream.Null; + } + else + { + seekableStream = new MultiBufferMemoryStream(this.ServiceClient.BufferManager); + seekableStreamCreated = true; + writeToStream = seekableStream; + } - StreamDescriptor streamCopyState = new StreamDescriptor(); - long startPosition = seekableStream.Position; - await rangeDataAsStream.WriteToAsync(writeToStream, null /* copyLength */, Constants.MaxBlockSize, requiresContentMD5, tempExecutionState, streamCopyState, cancellationToken); - seekableStream.Position = startPosition; + StreamDescriptor streamCopyState = new StreamDescriptor(); + long startPosition = seekableStream.Position; + await rangeDataAsStream.WriteToAsync(writeToStream, null /* copyLength */, Constants.MaxBlockSize, requiresContentMD5, tempExecutionState, streamCopyState, cancellationToken); + seekableStream.Position = startPosition; - if (requiresContentMD5) - { - contentMD5 = streamCopyState.Md5; + if (requiresContentMD5) + { + contentMD5 = streamCopyState.Md5; + } + + if (modifiedOptions.MaximumExecutionTime.HasValue) + { + modifiedOptions.MaximumExecutionTime -= DateTime.Now.Subtract(streamCopyStartTime); + } } - if (modifiedOptions.MaximumExecutionTime.HasValue) + await Executor.ExecuteAsyncNullReturn( + this.PutRangeImpl(seekableStream, startOffset, contentMD5, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken); + } + finally + { + if (seekableStreamCreated) { - modifiedOptions.MaximumExecutionTime -= DateTime.Now.Subtract(streamCopyStartTime); + seekableStream.Dispose(); } } - - await Executor.ExecuteAsyncNullReturn( - this.PutRangeImpl(seekableStream, startOffset, contentMD5, accessCondition, modifiedOptions), - modifiedOptions.RetryPolicy, - operationContext, - cancellationToken); #if ASPNET_K }, cancellationToken); #else @@ -1767,11 +1780,214 @@ public Task ClearRangeAsync(long startOffset, long length, AccessCondition acces #endif /// - /// Implements getting the file. + /// Begins an operation to start copying an existing blob or Azure file's contents, properties, and metadata to a new Azure file. /// - /// An object that represents the access conditions for the file. If null, no condition is used. - /// An object that specifies additional options for the request. - /// A that gets the stream. + /// The URI of a source object. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the file's ETag, last modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] +#if ASPNET_K + public Task StartCopyAsync(Uri source) +#else + [DefaultOverload] + public IAsyncOperation StartCopyAsync(Uri source) +#endif + { + return this.StartCopyAsync(source, null /* sourceAccessCondition */, null /* destAccessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Begins an operation to start copying an existing blob's contents, properties, and metadata to a new Azure file. + /// + /// The source blob. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the file's ETag, last modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] +#if ASPNET_K + public Task StartCopyAsync(CloudBlob source) +#else + public IAsyncOperation StartCopyAsync(CloudBlob source) +#endif + { + return this.StartCopyAsync(CloudBlob.SourceBlobToUri(source)); + } + + /// + /// Begins an operation to start copying an existing Azure file's contents, properties, and metadata to a new Azure file. + /// + /// The source file. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the file's ETag, last modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] +#if ASPNET_K + public Task StartCopyAsync(CloudFile source) +#else + public IAsyncOperation StartCopyAsync(CloudFile source) +#endif + { + return this.StartCopyAsync(CloudFile.SourceFileToUri(source)); + } + + /// + /// Begins an operation to start copying a blob or file's contents, properties, and metadata to a new Azure file. + /// + /// The URI of a source object. + /// An object that represents the access conditions for the source object. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the file's ETag, last modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] +#if ASPNET_K + public Task StartCopyAsync(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext) + { + return StartCopyAsync(source, sourceAccessCondition, destAccessCondition, options, operationContext, CancellationToken.None); + } +#else + [DefaultOverload] + public IAsyncOperation StartCopyAsync(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext) + { + CommonUtility.AssertNotNull("source", source); + + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return AsyncInfo.Run(async (token) => await Executor.ExecuteAsync( + this.StartCopyImpl(source, sourceAccessCondition, destAccessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + token)); + } +#endif + +#if ASPNET_K + /// + /// Begins an operation to start copying a blob or file's contents, properties, and metadata to a new Azure file. + /// + /// The URI of a source object. + /// An object that represents the access conditions for the source object. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the file's ETag, last modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] + public Task StartCopyAsync(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Task.Run(async () => await Executor.ExecuteAsync( + this.StartCopyImpl(source, sourceAccessCondition, destAccessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken), cancellationToken); + } +#endif + + /// + /// Begins an operation to start copying a blob's contents, properties, and metadata to a new Azure file. + /// + /// The source blob. + /// An object that represents the access conditions for the source blob. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// The copy ID associated with the copy operation. + /// + /// This method fetches the file's ETag, last modified time, and part of the copy state. + /// The copy ID and copy status fields are fetched, and the rest of the copy state is cleared. + /// + [DoesServiceRequest] +#if ASPNET_K + public Task StartCopyAsync(CloudBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext) +#else + public IAsyncOperation StartCopyAsync(CloudBlob source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options, OperationContext operationContext) +#endif + + { + return this.StartCopyAsync(CloudBlob.SourceBlobToUri(source), sourceAccessCondition, destAccessCondition, options, operationContext); + } + + /// + /// Aborts an ongoing copy operation. + /// + /// A string identifying the copy operation. + /// An that represents an asynchronous action. + [DoesServiceRequest] +#if ASPNET_K + public Task AbortCopyAsync(string copyId) +#else + public IAsyncAction AbortCopyAsync(string copyId) +#endif + + { + return this.AbortCopyAsync(copyId, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Aborts an ongoing copy operation. + /// + /// A string identifying the copy operation. + /// An object that represents the access conditions for the file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] +#if ASPNET_K + public Task AbortCopyAsync(string copyId, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + return this.AbortCopyAsync(copyId, accessCondition, options, operationContext, CancellationToken.None); + } +#else + public IAsyncAction AbortCopyAsync(string copyId, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return AsyncInfo.Run(async (token) => await Executor.ExecuteAsyncNullReturn( + this.AbortCopyImpl(copyId, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + token)); + } +#endif + +#if ASPNET_K + /// + /// Aborts an ongoing copy operation. + /// + /// A string identifying the copy operation. + /// An object that represents the access conditions for the file. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] + public Task AbortCopyAsync(string copyId, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( + this.AbortCopyImpl(copyId, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken), cancellationToken); + } +#endif + + /// + /// Implements getting the file. + /// + /// An object that represents the access conditions for the file. If null, no condition is used. + /// An object that specifies additional options for the request. + /// A that gets the stream. private RESTCommand GetFileImpl(Stream destStream, long? offset, long? length, AccessCondition accessCondition, FileRequestOptions options) { string lockedETag = null; @@ -2182,6 +2398,79 @@ private RESTCommand ClearRangeImpl(long startOffset, long length, Acce return putCmd; } + /// + /// Implementation of the StartCopy method. Result is a CloudFileAttributes object derived from the response headers. + /// + /// The URI of the source object. + /// An object that represents the access conditions for the source object. If null, no condition is used. + /// An object that represents the access conditions for the destination file. If null, no condition is used. + /// An object that specifies additional options for the request. + /// A delegate for setting the CloudFileAttributes result. + /// A that starts to copy the object. + internal RESTCommand StartCopyImpl(Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, FileRequestOptions options) + { + if (sourceAccessCondition != null && !string.IsNullOrEmpty(sourceAccessCondition.LeaseId)) + { + throw new ArgumentException(SR.LeaseConditionOnSource, "sourceAccessCondition"); + } + + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.Handler = this.ServiceClient.AuthenticationHandler; + putCmd.BuildClient = HttpClientFactory.BuildHttpClient; + putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => + { + HttpRequestMessage msg = FileHttpRequestMessageFactory.CopyFrom(uri, serverTimeout, source, sourceAccessCondition, destAccessCondition, cnt, ctx); + FileHttpRequestMessageFactory.AddMetadata(msg, attributes.Metadata); + return msg; + }; + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Accepted, resp, null /* retVal */, cmd, ex); + CopyState state = FileHttpResponseParsers.GetCopyAttributes(resp); + this.attributes.Properties = FileHttpResponseParsers.GetProperties(resp); + this.attributes.Metadata = FileHttpResponseParsers.GetMetadata(resp); + this.attributes.CopyState = state; + return state.CopyId; + }; + + return putCmd; + } + + /// + /// Implementation of the AbortCopy method. No result is produced. + /// + /// The copy ID of the copy operation to abort. + /// An object that represents the access conditions for the operation. If null, no condition is used. + /// An object that specifies additional options for the request. + /// A that copies the object. + internal RESTCommand AbortCopyImpl(string copyId, AccessCondition accessCondition, FileRequestOptions options) + { + CommonUtility.AssertNotNull("copyId", copyId); + + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.attributes.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.Handler = this.ServiceClient.AuthenticationHandler; + putCmd.BuildClient = HttpClientFactory.BuildHttpClient; + putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => FileHttpRequestMessageFactory.AbortCopy(uri, serverTimeout, copyId, accessCondition, cnt, ctx); + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.NoContent, resp, NullType.Value, cmd, ex); + + return putCmd; + } + + /// + /// Converts the source file of a copy operation to an appropriate access URI, taking Shared Access Signature credentials into account. + /// + /// The source file. + /// A URI addressing the source file, using SAS if appropriate. + internal static Uri SourceFileToUri(CloudFile source) + { + CommonUtility.AssertNotNull("source", source); + return source.ServiceClient.Credentials.TransformUri(source.Uri); + } + /// /// Updates this file with the given attributes a the end of a fetch attributes operation. /// diff --git a/Lib/WindowsRuntime/File/CloudFileClient.cs b/Lib/WindowsRuntime/File/CloudFileClient.cs index ce80e6d4a..0ccf4ae12 100644 --- a/Lib/WindowsRuntime/File/CloudFileClient.cs +++ b/Lib/WindowsRuntime/File/CloudFileClient.cs @@ -18,10 +18,12 @@ namespace Microsoft.WindowsAzure.Storage.File { using Microsoft.WindowsAzure.Storage.Auth.Protocol; + using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Executor; using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.File.Protocol; using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -169,6 +171,134 @@ public Task ListSharesSegmentedAsync(string prefix, ShareLis }, cancellationToken); } #endif + /// + /// Gets the properties of the File service. + /// + /// The File service properties. + [DoesServiceRequest] +#if ASPNET_K + public Task GetServicePropertiesAsync() +#else + public IAsyncOperation GetServicePropertiesAsync() +#endif + { + return this.GetServicePropertiesAsync(null /* options */, null /* operationContext */); + } + + /// + /// Gets the properties of the File service. + /// + /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. + /// An object that represents the context for the current operation. + /// The File service properties. + [DoesServiceRequest] +#if ASPNET_K + public Task GetServicePropertiesAsync(FileRequestOptions options, OperationContext operationContext) + { + return this.GetServicePropertiesAsync(options, operationContext, CancellationToken.None); + } +#else + public IAsyncOperation GetServicePropertiesAsync(FileRequestOptions options, OperationContext operationContext) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this); + operationContext = operationContext ?? new OperationContext(); + + return AsyncInfo.Run( + async (token) => await Executor.ExecuteAsync( + this.GetServicePropertiesImpl(modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + token)); + } +#endif + +#if ASPNET_K + /// + /// Gets the properties of the File service. + /// + /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// The File service properties. + [DoesServiceRequest] + public Task GetServicePropertiesAsync(FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this); + operationContext = operationContext ?? new OperationContext(); + + return Task.Run( + async () => await Executor.ExecuteAsync( + this.GetServicePropertiesImpl(modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken), cancellationToken); + } +#endif + + + /// + /// Sets the properties of the File service. + /// + /// The File service properties. + /// The properties of the File service. + [DoesServiceRequest] +#if ASPNET_K + public Task SetServicePropertiesAsync(FileServiceProperties properties) +#else + public IAsyncAction SetServicePropertiesAsync(FileServiceProperties properties) +#endif + { + return this.SetServicePropertiesAsync(properties, null /* options */, null /* operationContext */); + } + + /// + /// Gets the properties of the File service. + /// + /// The File service properties. + /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. + /// An object that represents the context for the current operation. + /// The properties of the File service. + [DoesServiceRequest] +#if ASPNET_K + public Task SetServicePropertiesAsync(FileServiceProperties properties, FileRequestOptions requestOptions, OperationContext operationContext) + { + return this.SetServicePropertiesAsync(properties, requestOptions, operationContext, CancellationToken.None); + } +#else + public IAsyncAction SetServicePropertiesAsync(FileServiceProperties properties, FileRequestOptions requestOptions, OperationContext operationContext) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(requestOptions, this); + operationContext = operationContext ?? new OperationContext(); + return AsyncInfo.Run(async (token) => await Executor.ExecuteAsyncNullReturn( + this.SetServicePropertiesImpl(properties, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + token)); + } +#endif + +#if ASPNET_K + /// + /// Gets the properties of the File service. + /// + /// The File service properties. + /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// The properties of the File service. + [DoesServiceRequest] + public Task SetServicePropertiesAsync(FileServiceProperties properties, FileRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(requestOptions, this); + operationContext = operationContext ?? new OperationContext(); + return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( + this.SetServicePropertiesImpl(properties, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken), cancellationToken); + } +#endif + /// /// Core implementation for the ListShares method. /// @@ -219,5 +349,54 @@ private RESTCommand> ListSharesImpl(string prefix, return getCmd; } + + private RESTCommand GetServicePropertiesImpl(FileRequestOptions requestOptions) + { + RESTCommand retCmd = new RESTCommand(this.Credentials, this.StorageUri); + + retCmd.CommandLocationMode = CommandLocationMode.PrimaryOrSecondary; + retCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => FileHttpRequestMessageFactory.GetServiceProperties(uri, serverTimeout, ctx); + retCmd.RetrieveResponseStream = true; + retCmd.Handler = this.AuthenticationHandler; + retCmd.BuildClient = HttpClientFactory.BuildHttpClient; + retCmd.PreProcessResponse = + (cmd, resp, ex, ctx) => + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, null /* retVal */, cmd, ex); + + retCmd.PostProcessResponse = (cmd, resp, ctx) => + { + return Task.Factory.StartNew(() => FileHttpResponseParsers.ReadServiceProperties(cmd.ResponseStream)); + }; + + requestOptions.ApplyToStorageCommand(retCmd); + return retCmd; + } + + private RESTCommand SetServicePropertiesImpl(FileServiceProperties properties, FileRequestOptions requestOptions) + { + MultiBufferMemoryStream memoryStream = new MultiBufferMemoryStream(null /* bufferManager */, (int)(1 * Constants.KB)); + try + { + properties.WriteServiceProperties(memoryStream); + } + catch (InvalidOperationException invalidOpException) + { + throw new ArgumentException(invalidOpException.Message, "properties"); + } + + RESTCommand retCmd = new RESTCommand(this.Credentials, this.StorageUri); + requestOptions.ApplyToStorageCommand(retCmd); + retCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => FileHttpRequestMessageFactory.SetServiceProperties(uri, serverTimeout, cnt, ctx); + retCmd.BuildContent = (cmd, ctx) => HttpContentFactory.BuildContentFromStream(memoryStream, 0, memoryStream.Length, null /* md5 */, cmd, ctx); + retCmd.StreamToDispose = memoryStream; + retCmd.RetrieveResponseStream = true; + retCmd.Handler = this.AuthenticationHandler; + retCmd.BuildClient = HttpClientFactory.BuildHttpClient; + retCmd.PreProcessResponse = + (cmd, resp, ex, ctx) => + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Accepted, resp, null /* retVal */, cmd, ex); + requestOptions.ApplyToStorageCommand(retCmd); + return retCmd; + } } } diff --git a/Lib/WindowsRuntime/File/CloudFileDirectory.cs b/Lib/WindowsRuntime/File/CloudFileDirectory.cs index b3227c743..e15e906be 100644 --- a/Lib/WindowsRuntime/File/CloudFileDirectory.cs +++ b/Lib/WindowsRuntime/File/CloudFileDirectory.cs @@ -516,6 +516,63 @@ public Task ListFilesAndDirectoriesSegmentedAsync(int? maxRes } #endif + /// + /// Updates the directory's metadata. + /// + [DoesServiceRequest] +#if ASPNET_K + public Task SetMetdataAsync() +#else + public IAsyncAction SetMetadataAsync() +#endif + { + return this.SetMetadataAsync(null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Updates the directory's metadata. + /// + /// An object that represents the access conditions for the directory. If null, no condition is used. + /// An object that specifies additional options for the request. + /// An object that represents the context for the current operation. + [DoesServiceRequest] +#if ASPNET_K + public Task SetMetadataAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + return this.SetMetadataAsync(accessCondition, options, operationContext, CancellationToken.None); + } +#else + public IAsyncAction SetMetadataAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return AsyncInfo.Run(async (token) => await Executor.ExecuteAsyncNullReturn( + this.SetMetadataImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + token)); + } +#endif + +#if ASPNET_K + /// + /// Updates the directory's metadata. + /// + /// An object that represents the access conditions for the directory. If null, no condition is used. + /// An object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// Cancellation Token + [DoesServiceRequest] + public Task SetMetadataAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( + this.SetMetadataImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken), cancellationToken); + } +#endif + /// /// Implementation for the Create method. /// @@ -528,7 +585,12 @@ private RESTCommand CreateDirectoryImpl(FileRequestOptions options) options.ApplyToStorageCommand(putCmd); putCmd.Handler = this.ServiceClient.AuthenticationHandler; putCmd.BuildClient = HttpClientFactory.BuildHttpClient; - putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => DirectoryHttpRequestMessageFactory.Create(uri, serverTimeout, cnt, ctx); + putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => + { + HttpRequestMessage msg = DirectoryHttpRequestMessageFactory.Create(uri, serverTimeout, cnt, ctx); + DirectoryHttpRequestMessageFactory.AddMetadata(msg, this.Metadata); + return msg; + }; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); @@ -604,6 +666,7 @@ private RESTCommand FetchAttributesImpl(AccessCondition accessConditio { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); this.Properties = DirectoryHttpResponseParsers.GetProperties(resp); + this.Metadata = DirectoryHttpResponseParsers.GetMetadata(resp); return NullType.Value; }; @@ -660,6 +723,35 @@ private RESTCommand> ListFilesAndDirectoriesImpl(in return getCmd; } + /// + /// Implementation for the SetMetadata method. + /// + /// An object that represents the access conditions for the directory. If null, no condition is used. + /// An object that specifies additional options for the request. + /// A that sets the metadata. + private RESTCommand SetMetadataImpl(AccessCondition accessCondition, FileRequestOptions options) + { + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.Handler = this.ServiceClient.AuthenticationHandler; + putCmd.BuildClient = HttpClientFactory.BuildHttpClient; + putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => + { + HttpRequestMessage msg = DirectoryHttpRequestMessageFactory.SetMetadata(uri, serverTimeout, accessCondition, cnt, ctx); + DirectoryHttpRequestMessageFactory.AddMetadata(msg, this.Metadata); + return msg; + }; + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); + this.UpdateETagAndLastModified(resp); + return NullType.Value; + }; + + return putCmd; + } + /// /// Retrieve ETag and LastModified date time from response. /// diff --git a/Lib/WindowsRuntime/File/CloudFileShare.cs b/Lib/WindowsRuntime/File/CloudFileShare.cs index 587309097..c9f28ed12 100644 --- a/Lib/WindowsRuntime/File/CloudFileShare.cs +++ b/Lib/WindowsRuntime/File/CloudFileShare.cs @@ -20,9 +20,11 @@ namespace Microsoft.WindowsAzure.Storage.File using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Executor; + using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.File.Protocol; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; + using System.Collections.Generic; using System.Net; using System.Net.Http; #if ASPNET_K @@ -30,6 +32,7 @@ namespace Microsoft.WindowsAzure.Storage.File using System.Threading.Tasks; #else using System.Runtime.InteropServices.WindowsRuntime; + using System.Threading.Tasks; using Windows.Foundation; #endif @@ -215,8 +218,8 @@ public IAsyncAction DeleteAsync(AccessCondition accessCondition, FileRequestOpti FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); return AsyncInfo.Run(async (token) => await Executor.ExecuteAsyncNullReturn( this.DeleteShareImpl(accessCondition, modifiedOptions), - modifiedOptions.RetryPolicy, - operationContext, + modifiedOptions.RetryPolicy, + operationContext, token)); } #endif @@ -258,6 +261,7 @@ public IAsyncOperation DeleteIfExistsAsync() /// /// Deletes the share if it already exists. /// + /// An object that represents the access conditions for the share. If null, no condition is used. /// An object that specifies additional options for the request. /// An object that represents the context for the current operation. /// true if the share already existed and was deleted; otherwise, false. @@ -453,8 +457,255 @@ public Task FetchAttributesAsync(AccessCondition accessCondition, FileRequestOpt #endif /// - /// Sets the share's user-defined metadata. + /// Sets permissions for the share. + /// + /// The permissions to apply to the share. + /// An that represents an asynchronous action. + [DoesServiceRequest] +#if ASPNET_K + /// A that represents an asynchronous action. + public Task SetPermissionsAsync(FileSharePermissions permissions) +#else + public IAsyncAction SetPermissionsAsync(FileSharePermissions permissions) +#endif + { + return this.SetPermissionsAsync(permissions, null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Sets permissions for the share. + /// + /// The permissions to apply to the share. + /// An object that represents the access conditions for the share. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An that represents an asynchronous action. + [DoesServiceRequest] +#if ASPNET_K + /// A that represents an asynchronous action. + public Task SetPermissionsAsync(FileSharePermissions permissions, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + return this.SetPermissionsAsync(permissions, accessCondition, options, operationContext, CancellationToken.None); + } +#else + public IAsyncAction SetPermissionsAsync(FileSharePermissions permissions, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return AsyncInfo.Run(async (token) => await Executor.ExecuteAsyncNullReturn( + this.SetPermissionsImpl(permissions, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + token)); + } +#endif + +#if ASPNET_K + /// + /// Sets permissions for the share. + /// + /// The permissions to apply to the container. + /// An object that represents the access conditions for the container. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// A to observe while waiting for a task to complete. + /// A that represents an asynchronous action. + [DoesServiceRequest] + public Task SetPermissionsAsync(FileSharePermissions permissions, AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( + this.SetPermissionsImpl(permissions, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken), cancellationToken); + } +#endif + + /// + /// Updates the share's properties. /// + /// An that represents an asynchronous action. + [DoesServiceRequest] +#if ASPNET_K + /// A that represents an asynchronous action. + public Task SetPropertiesAsync() +#else + public IAsyncAction SetPropertiesAsync() +#endif + { + return this.SetPropertiesAsync(null /* accessCondition */, null /* options */, null /* operationContext */); + } + + + /// + /// Updates the share's properties. + /// + /// An object that represents the access conditions for the share. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An that represents an asynchronous action. + [DoesServiceRequest] +#if ASPNET_K + public Task SetPropertiesAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + return this.SetPropertiesAsync(accessCondition, options, operationContext, CancellationToken.None); + } +#else + public IAsyncAction SetPropertiesAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return AsyncInfo.Run(async (token) => await Executor.ExecuteAsyncNullReturn( + this.SetPropertiesImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + token)); + } +#endif + +#if ASPNET_K + /// + /// Updates the share's properties. + /// + /// An object that represents the access conditions for the share. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// An that represents an asynchronous action. + [DoesServiceRequest] + public Task SetPropertiesAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Task.Run(async () => await Executor.ExecuteAsyncNullReturn( + this.SetPropertiesImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken), cancellationToken); + } +#endif + + + /// + /// Gets the permissions settings for the share. + /// + /// The share's permissions. + [DoesServiceRequest] +#if ASPNET_K + public Task GetPermissionsAsync() +#else + public IAsyncOperation GetPermissionsAsync() +#endif + { + return this.GetPermissionsAsync(null /* accessCondition */, null /* options */, null /* operationContext */); + } + + /// + /// Gets the permissions settings for the share. + /// + /// An object that represents the access conditions for the share. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// The share's permissions. + [DoesServiceRequest] +#if ASPNET_K + public Task GetPermissionsAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + return this.GetPermissionsAsync(accessCondition, options, operationContext, CancellationToken.None); + } +#else + + public IAsyncOperation GetPermissionsAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return AsyncInfo.Run(async (token) => await Executor.ExecuteAsync( + this.GetPermissionsImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + token)); + } +#endif + +#if ASPNET_K + /// + /// Gets the permissions settings for the share. + /// + /// An object that represents the access conditions for the share. If null, no condition is used. + /// A object that specifies additional options for the request. + /// An object that represents the context for the current operation. + /// The share's permissions. + [DoesServiceRequest] + public Task GetPermissionsAsync(AccessCondition accessCondition, FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Task.Run(async () => await Executor.ExecuteAsync( + this.GetPermissionsImpl(accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken), cancellationToken); + } +#endif + + /// + /// Gets the stats of share. + /// + /// The share stats. + [DoesServiceRequest] +#if ASPNET_K + public Task GetStatsAsync() +#else + public IAsyncOperation GetStatsAsync() +#endif + { + return this.GetStatsAsync(null /* options */, null /* operationContext */); + } + + /// + /// Gets the stats of the share. + /// + /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. + /// An object that represents the context for the current operation. + /// The share stats. + [DoesServiceRequest] +#if ASPNET_K + public Task GetStatsAsync(FileRequestOptions options, OperationContext operationContext) + { + return this.GetStatsAsync(options, operationContext, CancellationToken.None); + } +#else + public IAsyncOperation GetStatsAsync(FileRequestOptions options, OperationContext operationContext) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + operationContext = operationContext ?? new OperationContext(); + + return AsyncInfo.Run( + async (token) => await Executor.ExecuteAsync( + this.GetStatsImpl(modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + token)); + } +#endif + +#if ASPNET_K + /// + /// Gets the stats of the share. + /// + /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. + /// An object that represents the context for the current operation. + /// The share stats. + [DoesServiceRequest] + public Task GetStatsAsync(FileRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) + { + FileRequestOptions modifiedOptions = FileRequestOptions.ApplyDefaults(options, this.ServiceClient); + return Task.Run(async () => await Executor.ExecuteAsync( + this.GetStatsImpl(modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext, + cancellationToken), cancellationToken); + } +#endif + + /// + /// Sets the share's user-defined metadata. + /// [DoesServiceRequest] #if ASPNET_K public Task SetMetadataAsync() @@ -523,7 +774,7 @@ private RESTCommand CreateShareImpl(FileRequestOptions options) putCmd.BuildClient = HttpClientFactory.BuildHttpClient; putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => { - HttpRequestMessage msg = ShareHttpRequestMessageFactory.Create(uri, serverTimeout, cnt, ctx); + HttpRequestMessage msg = ShareHttpRequestMessageFactory.Create(uri, this.Properties, serverTimeout, cnt, ctx); ShareHttpRequestMessageFactory.AddMetadata(msg, this.Metadata); return msg; }; @@ -537,7 +788,7 @@ private RESTCommand CreateShareImpl(FileRequestOptions options) return putCmd; } - + /// /// Implementation for the Delete method. /// @@ -613,6 +864,92 @@ private RESTCommand ExistsImpl(FileRequestOptions options) return getCmd; } + /// + /// Implementation for the SetPermissions method. + /// + /// The permissions to set. + /// An object that represents the access conditions for the share. If null, no condition is used. + /// A object that specifies additional options for the request. + /// A that sets the permissions. + private RESTCommand SetPermissionsImpl(FileSharePermissions acl, AccessCondition accessCondition, FileRequestOptions options) + { + MultiBufferMemoryStream memoryStream = new MultiBufferMemoryStream(null /* bufferManager */, (int)(1 * Constants.KB)); + FileRequest.WriteSharedAccessIdentifiers(acl.SharedAccessPolicies, memoryStream); + + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.Handler = this.ServiceClient.AuthenticationHandler; + putCmd.BuildClient = HttpClientFactory.BuildHttpClient; + putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => ShareHttpRequestMessageFactory.SetAcl(uri, serverTimeout, FileSharePublicAccessType.Off, accessCondition, cnt, ctx); + putCmd.BuildContent = (cmd, ctx) => HttpContentFactory.BuildContentFromStream(memoryStream, 0, memoryStream.Length, null /* md5 */, cmd, ctx); + putCmd.StreamToDispose = memoryStream; + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); + this.UpdateETagAndLastModified(resp); + return NullType.Value; + }; + + return putCmd; + } + + /// + /// Implementation for the GetPermissions method. + /// + /// An object that represents the access conditions for the share. If null, no condition is used. + /// A object that specifies additional options for the request. + /// A that gets the permissions. + private RESTCommand GetPermissionsImpl(AccessCondition accessCondition, FileRequestOptions options) + { + FileSharePermissions shareAcl = null; + + RESTCommand getCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); + + options.ApplyToStorageCommand(getCmd); + getCmd.CommandLocationMode = CommandLocationMode.PrimaryOrSecondary; + getCmd.RetrieveResponseStream = true; + getCmd.Handler = this.ServiceClient.AuthenticationHandler; + getCmd.BuildClient = HttpClientFactory.BuildHttpClient; + getCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => ShareHttpRequestMessageFactory.GetAcl(uri, serverTimeout, accessCondition, cnt, ctx); + getCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, null /* retVal */, cmd, ex); + shareAcl = new FileSharePermissions(); + return shareAcl; + }; + getCmd.PostProcessResponse = (cmd, resp, ctx) => + { + this.UpdateETagAndLastModified(resp); + return Task.Factory.StartNew(() => + { + ShareHttpResponseParsers.ReadSharedAccessIdentifiers(cmd.ResponseStream, shareAcl); + return shareAcl; + }); + }; + + return getCmd; + } + + /// + /// Implementation for the GetStats method. + /// + /// A object that specifies additional options for the request. + /// A that gets the share stats. + private RESTCommand GetStatsImpl(FileRequestOptions requestOptions) + { + RESTCommand retCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); + requestOptions.ApplyToStorageCommand(retCmd); + retCmd.CommandLocationMode = CommandLocationMode.PrimaryOrSecondary; + retCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => ShareHttpRequestMessageFactory.GetStats(uri, serverTimeout, ctx); + retCmd.RetrieveResponseStream = true; + retCmd.Handler = this.ServiceClient.AuthenticationHandler; + retCmd.BuildClient = HttpClientFactory.BuildHttpClient; + retCmd.PreProcessResponse = (cmd, resp, ex, ctx) => HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, null /* retVal */, cmd, ex); + retCmd.PostProcessResponse = (cmd, resp, ctx) => Task.Factory.StartNew(() => ShareHttpResponseParsers.ReadShareStats(cmd.ResponseStream)); + return retCmd; + } + /// /// Implementation for the SetMetadata method. /// @@ -642,6 +979,35 @@ private RESTCommand SetMetadataImpl(AccessCondition accessCondition, F return putCmd; } + /// + /// Implementation for the SetProperties method. + /// + /// An object that represents the access conditions for the share. If null, no condition is used. + /// An object that specifies additional options for the request. + /// A that sets the metadata. + private RESTCommand SetPropertiesImpl(AccessCondition accessCondition, FileRequestOptions options) + { + RESTCommand putCmd = new RESTCommand(this.ServiceClient.Credentials, this.StorageUri); + + options.ApplyToStorageCommand(putCmd); + putCmd.Handler = this.ServiceClient.AuthenticationHandler; + putCmd.BuildClient = HttpClientFactory.BuildHttpClient; + putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => + { + HttpRequestMessage msg = ShareHttpRequestMessageFactory.SetProperties(uri, serverTimeout, this.Properties, accessCondition, cnt, ctx); + ShareHttpRequestMessageFactory.AddMetadata(msg, this.Metadata); + return msg; + }; + putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => + { + HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.OK, resp, NullType.Value, cmd, ex); + this.UpdateETagAndLastModified(resp); + return NullType.Value; + }; + + return putCmd; + } + /// /// Retrieve ETag and LastModified date time from response. /// diff --git a/Lib/WindowsRuntime/File/ICloudFileStream.cs b/Lib/WindowsRuntime/File/ICloudFileStream.cs index 4b7a4438e..f849a494a 100644 --- a/Lib/WindowsRuntime/File/ICloudFileStream.cs +++ b/Lib/WindowsRuntime/File/ICloudFileStream.cs @@ -27,7 +27,7 @@ public abstract class CloudFileStream : Stream /// /// Asynchronously clears all buffers for this stream, causes any buffered data to be written to the underlying file, and commits the file. /// - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. public abstract Task CommitAsync(); internal Stream AsStreamForWrite() diff --git a/Lib/WindowsRuntime/File/Protocol/DirectoryHttpRequestMessageFactory.cs b/Lib/WindowsRuntime/File/Protocol/DirectoryHttpRequestMessageFactory.cs index 5889265fa..a07221b08 100644 --- a/Lib/WindowsRuntime/File/Protocol/DirectoryHttpRequestMessageFactory.cs +++ b/Lib/WindowsRuntime/File/Protocol/DirectoryHttpRequestMessageFactory.cs @@ -21,6 +21,7 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; + using System.Collections.Generic; using System.Net.Http; internal static class DirectoryHttpRequestMessageFactory @@ -62,11 +63,28 @@ public static HttpRequestMessage Delete(Uri uri, int? timeout, AccessCondition a public static HttpRequestMessage GetProperties(Uri uri, int? timeout, AccessCondition accessCondition, HttpContent content, OperationContext operationContext) { UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); + HttpRequestMessage request = HttpRequestMessageFactory.GetProperties(uri, timeout, directoryBuilder, content, operationContext); request.ApplyAccessCondition(accessCondition); return request; } + /// + /// Generates a web request to return the user-defined metadata for this directory. + /// + /// The absolute URI to the directory. + /// The server timeout interval. + /// The access condition to apply to the request. + /// A web request to use to perform the operation. + public static HttpRequestMessage GetMetadata(Uri uri, int? timeout, AccessCondition accessCondition, HttpContent content, OperationContext operationContext) + { + UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); + + HttpRequestMessage request = HttpRequestMessageFactory.GetMetadata(uri, timeout, directoryBuilder, content, operationContext); + request.ApplyAccessCondition(accessCondition); + return request; + } + /// /// Generates a web request to return a listing of all files and subdirectories in the directory. /// @@ -76,26 +94,51 @@ public static HttpRequestMessage GetProperties(Uri uri, int? timeout, AccessCond /// A web request to use to perform the operation. public static HttpRequestMessage List(Uri uri, int? timeout, FileListingContext listingContext, HttpContent content, OperationContext operationContext) { - UriQueryBuilder builder = GetDirectoryUriQueryBuilder(); - builder.Add(Constants.QueryConstants.Component, "list"); + UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); + directoryBuilder.Add(Constants.QueryConstants.Component, "list"); if (listingContext != null) { if (listingContext.Marker != null) { - builder.Add("marker", listingContext.Marker); + directoryBuilder.Add("marker", listingContext.Marker); } if (listingContext.MaxResults.HasValue) { - builder.Add("maxresults", listingContext.MaxResults.ToString()); + directoryBuilder.Add("maxresults", listingContext.MaxResults.ToString()); } } - HttpRequestMessage request = HttpRequestMessageFactory.CreateRequestMessage(HttpMethod.Get, uri, timeout, builder, content, operationContext); + HttpRequestMessage request = HttpRequestMessageFactory.CreateRequestMessage(HttpMethod.Get, uri, timeout, directoryBuilder, content, operationContext); return request; } + /// + /// Constructs a web request to set user-defined metadata for the directory. + /// + /// The absolute URI to the directory. + /// The server timeout interval. + /// The access condition to apply to the request. + /// A web request for performing the operation. + public static HttpRequestMessage SetMetadata(Uri uri, int? timeout, AccessCondition accessCondition, HttpContent content, OperationContext operationContext) + { + UriQueryBuilder directoryBuilder = GetDirectoryUriQueryBuilder(); + HttpRequestMessage request = HttpRequestMessageFactory.SetMetadata(uri, timeout, directoryBuilder, content, operationContext); + request.ApplyAccessCondition(accessCondition); + return request; + } + + /// + /// Adds user-defined metadata to the request as one or more name-value pairs. + /// + /// The web request. + /// The user-defined metadata. + public static void AddMetadata(HttpRequestMessage request, IDictionary metadata) + { + HttpRequestMessageFactory.AddMetadata(request, metadata); + } + /// /// Gets the directory Uri query builder. /// diff --git a/Lib/WindowsRuntime/File/Protocol/DirectoryHttpResponseParsers.cs b/Lib/WindowsRuntime/File/Protocol/DirectoryHttpResponseParsers.cs index 6233cd956..6a22bc008 100644 --- a/Lib/WindowsRuntime/File/Protocol/DirectoryHttpResponseParsers.cs +++ b/Lib/WindowsRuntime/File/Protocol/DirectoryHttpResponseParsers.cs @@ -17,6 +17,8 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol { + using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System.Collections.Generic; using System.Net.Http; internal static partial class DirectoryHttpResponseParsers @@ -44,5 +46,15 @@ public static FileDirectoryProperties GetProperties(HttpResponseMessage response return directoryProperties; } + + /// + /// Gets the user-defined metadata. + /// + /// The response from server. + /// A of the metadata. + public static IDictionary GetMetadata(HttpResponseMessage response) + { + return HttpResponseParsers.GetMetadata(response); + } } } diff --git a/Lib/WindowsRuntime/File/Protocol/FileHttpRequestMessageFactory.cs b/Lib/WindowsRuntime/File/Protocol/FileHttpRequestMessageFactory.cs index 9b1cc3dd2..81cffb114 100644 --- a/Lib/WindowsRuntime/File/Protocol/FileHttpRequestMessageFactory.cs +++ b/Lib/WindowsRuntime/File/Protocol/FileHttpRequestMessageFactory.cs @@ -92,7 +92,9 @@ public static HttpRequestMessage Create(Uri uri, int? timeout, FileProperties pr /// A web request for performing the operation. public static HttpRequestMessage GetProperties(Uri uri, int? timeout, AccessCondition accessCondition, HttpContent content, OperationContext operationContext) { - HttpRequestMessage request = HttpRequestMessageFactory.GetProperties(uri, timeout, null /* builder */, content, operationContext); + UriQueryBuilder builder = new UriQueryBuilder(); + + HttpRequestMessage request = HttpRequestMessageFactory.GetProperties(uri, timeout, builder, content, operationContext); request.ApplyAccessCondition(accessCondition); return request; } @@ -106,7 +108,9 @@ public static HttpRequestMessage GetProperties(Uri uri, int? timeout, AccessCond /// A web request for performing the operation. public static HttpRequestMessage GetMetadata(Uri uri, int? timeout, AccessCondition accessCondition, HttpContent content, OperationContext operationContext) { - HttpRequestMessage request = HttpRequestMessageFactory.GetMetadata(uri, timeout, null /* builder */, content, operationContext); + UriQueryBuilder builder = new UriQueryBuilder(); + + HttpRequestMessage request = HttpRequestMessageFactory.GetMetadata(uri, timeout, builder, content, operationContext); request.ApplyAccessCondition(accessCondition); return request; } @@ -197,7 +201,9 @@ private static void AddRange(HttpRequestMessage request, long? offset, long? cou /// A web request for performing the operation. public static HttpRequestMessage Get(Uri uri, int? timeout, AccessCondition accessCondition, HttpContent content, OperationContext operationContext) { - HttpRequestMessage request = HttpRequestMessageFactory.CreateRequestMessage(HttpMethod.Get, uri, timeout, null /* builder */, content, operationContext); + UriQueryBuilder builder = new UriQueryBuilder(); + + HttpRequestMessage request = HttpRequestMessageFactory.CreateRequestMessage(HttpMethod.Get, uri, timeout, builder, content, operationContext); request.ApplyAccessCondition(accessCondition); return request; } @@ -333,5 +339,70 @@ public static HttpRequestMessage PutRange(Uri uri, int? timeout, FileRange fileR request.ApplyAccessCondition(accessCondition); return request; } + + /// + /// Generates a web request to copy. + /// + /// The absolute URI to the destination file. + /// The server timeout interval. + /// The absolute URI to the source object, including any necessary authentication parameters. + /// The access condition to apply to the source object. + /// The access condition to apply to the destination file. + /// A web request to use to perform the operation. + public static HttpRequestMessage CopyFrom(Uri uri, int? timeout, Uri source, AccessCondition sourceAccessCondition, AccessCondition destAccessCondition, HttpContent content, OperationContext operationContext) + { + HttpRequestMessage request = HttpRequestMessageFactory.CreateRequestMessage(HttpMethod.Put, uri, timeout, null /* builder */, content, operationContext); + + request.Headers.Add(Constants.HeaderConstants.CopySourceHeader, source.AbsoluteUri); + request.ApplyAccessCondition(destAccessCondition); + request.ApplyAccessConditionToSource(sourceAccessCondition); + + return request; + } + + /// + /// Constructs a web request to get the properties of the service. + /// + /// The absolute URI to the service. + /// The server timeout interval. + /// A HttpRequestMessage to get the service properties. + public static HttpRequestMessage GetServiceProperties(Uri uri, int? timeout, OperationContext operationContext) + { + return HttpRequestMessageFactory.GetServiceProperties(uri, timeout, operationContext); + } + + /// + /// Creates a web request to set the properties of the service. + /// + /// The absolute URI to the service. + /// The server timeout interval. + /// A web request to set the service properties. + internal static HttpRequestMessage SetServiceProperties(Uri uri, int? timeout, HttpContent content, OperationContext operationContext) + { + return HttpRequestMessageFactory.SetServiceProperties(uri, timeout, content, operationContext); + } + + /// + /// Generates a web request to abort a copy operation. + /// + /// The absolute URI to the file. + /// The server timeout interval. + /// The ID string of the copy operation to be aborted. + /// The access condition to apply to the request. + /// Only lease conditions are supported for this operation. + /// A web request for performing the operation. + public static HttpRequestMessage AbortCopy(Uri uri, int? timeout, string copyId, AccessCondition accessCondition, HttpContent content, OperationContext operationContext) + { + UriQueryBuilder builder = new UriQueryBuilder(); + builder.Add(Constants.QueryConstants.Component, "copy"); + builder.Add(Constants.QueryConstants.CopyId, copyId); + + HttpRequestMessage request = HttpRequestMessageFactory.CreateRequestMessage(HttpMethod.Put, uri, timeout, builder, content, operationContext); + + request.Headers.Add(Constants.HeaderConstants.CopyActionHeader, Constants.HeaderConstants.CopyActionAbort); + request.ApplyAccessCondition(accessCondition); + + return request; + } } } diff --git a/Lib/WindowsRuntime/File/Protocol/FileHttpResponseParsers.cs b/Lib/WindowsRuntime/File/Protocol/FileHttpResponseParsers.cs index 60fc6850c..ae6da0b27 100644 --- a/Lib/WindowsRuntime/File/Protocol/FileHttpResponseParsers.cs +++ b/Lib/WindowsRuntime/File/Protocol/FileHttpResponseParsers.cs @@ -17,6 +17,8 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol { + using Microsoft.WindowsAzure.Storage.Blob; + using Microsoft.WindowsAzure.Storage.Blob.Protocol; using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; @@ -91,6 +93,16 @@ public static FileProperties GetProperties(HttpResponseMessage response) return properties; } + /// + /// Extracts a object from the headers of a web response. + /// + /// The HTTP web response. + /// A object, or null if the web response does not contain a copy status. + public static CopyState GetCopyAttributes(HttpResponseMessage response) + { + return BlobHttpResponseParsers.GetCopyAttributes(response); + } + /// /// Gets the user-defined metadata. /// diff --git a/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs b/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs index 8d36812f6..1a4054929 100644 --- a/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs +++ b/Lib/WindowsRuntime/File/Protocol/ShareHttpRequestMessageFactory.cs @@ -19,10 +19,12 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol { using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; using System; using System.Collections.Generic; using System.Net.Http; + using System.Text; internal static class ShareHttpRequestMessageFactory { @@ -30,12 +32,20 @@ internal static class ShareHttpRequestMessageFactory /// Constructs a web request to create a new share. /// /// The absolute URI to the share. + /// Properties to set on the share. /// The server timeout interval. /// A web request to use to perform the operation. - public static HttpRequestMessage Create(Uri uri, int? timeout, HttpContent content, OperationContext operationContext) + public static HttpRequestMessage Create(Uri uri, FileShareProperties properties, int? timeout, HttpContent content, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); - return HttpRequestMessageFactory.Create(uri, timeout, shareBuilder, content, operationContext); + + HttpRequestMessage request = HttpRequestMessageFactory.Create(uri, timeout, shareBuilder, content, operationContext); + if (properties != null && properties.Quota.HasValue) + { + request.AddOptionalHeader(Constants.HeaderConstants.ShareQuota, properties.Quota.Value); + } + + return request; } /// @@ -48,6 +58,7 @@ public static HttpRequestMessage Create(Uri uri, int? timeout, HttpContent conte public static HttpRequestMessage Delete(Uri uri, int? timeout, AccessCondition accessCondition, HttpContent content, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); + HttpRequestMessage request = HttpRequestMessageFactory.Delete(uri, timeout, shareBuilder, content, operationContext); request.ApplyAccessCondition(accessCondition); return request; @@ -63,6 +74,7 @@ public static HttpRequestMessage Delete(Uri uri, int? timeout, AccessCondition a public static HttpRequestMessage GetMetadata(Uri uri, int? timeout, AccessCondition accessCondition, HttpContent content, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); + HttpRequestMessage request = HttpRequestMessageFactory.GetMetadata(uri, timeout, shareBuilder, content, operationContext); request.ApplyAccessCondition(accessCondition); return request; @@ -78,6 +90,7 @@ public static HttpRequestMessage GetMetadata(Uri uri, int? timeout, AccessCondit public static HttpRequestMessage GetProperties(Uri uri, int? timeout, AccessCondition accessCondition, HttpContent content, OperationContext operationContext) { UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); + HttpRequestMessage request = HttpRequestMessageFactory.GetProperties(uri, timeout, shareBuilder, content, operationContext); request.ApplyAccessCondition(accessCondition); return request; @@ -98,6 +111,31 @@ public static HttpRequestMessage SetMetadata(Uri uri, int? timeout, AccessCondit return request; } + /// + /// Constructs a web request to set system properties for a share. + /// + /// The absolute URI to the share. + /// The server timeout interval. + /// The share's properties. + /// The access condition to apply to the request. + /// A web request to use to perform the operation. + public static HttpRequestMessage SetProperties(Uri uri, int? timeout, FileShareProperties properties, AccessCondition accessCondition, HttpContent content, OperationContext operationContext) + { + CommonUtility.AssertNotNull("properties", properties); + + UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); + shareBuilder.Add(Constants.QueryConstants.Component, "properties"); + + HttpRequestMessage request = HttpRequestMessageFactory.CreateRequestMessage(HttpMethod.Put, uri, timeout, shareBuilder, content, operationContext); + if (properties.Quota.HasValue) + { + request.AddOptionalHeader(Constants.HeaderConstants.ShareQuota, properties.Quota.Value); + } + + request.ApplyAccessCondition(accessCondition); + return request; + } + /// /// Adds user-defined metadata to the request as one or more name-value pairs. /// @@ -159,6 +197,52 @@ public static HttpRequestMessage List(Uri uri, int? timeout, ListingContext list return request; } + /// + /// Constructs a web request to return the ACL for a share. + /// + /// The absolute URI to the share. + /// The server timeout interval. + /// The access condition to apply to the request. + /// A web request to use to perform the operation. + public static HttpRequestMessage GetAcl(Uri uri, int? timeout, AccessCondition accessCondition, HttpContent content, OperationContext operationContext) + { + HttpRequestMessage request = HttpRequestMessageFactory.GetAcl(uri, timeout, GetShareUriQueryBuilder(), content, operationContext); + request.ApplyAccessCondition(accessCondition); + return request; + } + + /// + /// Constructs a web request to set the ACL for a share. + /// + /// The absolute URI to the share. + /// The server timeout interval. + /// The type of public access to allow for the share. + /// The access condition to apply to the request. + /// A web request to use to perform the operation. + public static HttpRequestMessage SetAcl(Uri uri, int? timeout, FileSharePublicAccessType publicAccess, AccessCondition accessCondition, HttpContent content, OperationContext operationContext) + { + HttpRequestMessage request = HttpRequestMessageFactory.SetAcl(uri, timeout, GetShareUriQueryBuilder(), content, operationContext); + + request.ApplyAccessCondition(accessCondition); + return request; + } + + /// + /// Constructs a web request to get the stats of the service. + /// + /// The absolute URI to the service. + /// The server timeout interval. + /// A HttpRequestMessage to get the service stats. + public static HttpRequestMessage GetStats(Uri uri, int? timeout, OperationContext operationContext) + { + UriQueryBuilder shareBuilder = GetShareUriQueryBuilder(); + shareBuilder.Add(Constants.QueryConstants.Component, "stats"); + + HttpRequestMessage request = HttpRequestMessageFactory.CreateRequestMessage(HttpMethod.Get, uri, timeout, shareBuilder, null /* content */, operationContext); + + return request; + } + /// /// Gets the share Uri query builder. /// diff --git a/Lib/WindowsRuntime/File/Protocol/ShareHttpResponseParsers.cs b/Lib/WindowsRuntime/File/Protocol/ShareHttpResponseParsers.cs index 62d43e1e4..c50a984a1 100644 --- a/Lib/WindowsRuntime/File/Protocol/ShareHttpResponseParsers.cs +++ b/Lib/WindowsRuntime/File/Protocol/ShareHttpResponseParsers.cs @@ -17,9 +17,17 @@ namespace Microsoft.WindowsAzure.Storage.File.Protocol { + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Core.Util; using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; using System.Net.Http; + using System.Xml; + using System.Xml.Linq; /// /// Provides a set of methods for parsing share responses from the File service. @@ -47,6 +55,12 @@ public static FileShareProperties GetProperties(HttpResponseMessage response) shareProperties.LastModified = null; } + string quota = response.Headers.GetHeaderSingleValueOrDefault(Constants.HeaderConstants.ShareQuota); + if (!string.IsNullOrEmpty(quota)) + { + shareProperties.Quota = int.Parse(quota, CultureInfo.InvariantCulture); + } + return shareProperties; } @@ -59,5 +73,32 @@ public static IDictionary GetMetadata(HttpResponseMessage respon { return HttpResponseParsers.GetMetadata(response); } + + /// + /// Reads the share access policies from a stream in XML. + /// + /// The stream of XML policies. + /// The permissions object to which the policies are to be written. + public static void ReadSharedAccessIdentifiers(Stream inputStream, FileSharePermissions permissions) + { + CommonUtility.AssertNotNull("permissions", permissions); + + Response.ReadSharedAccessIdentifiers(permissions.SharedAccessPolicies, new FileAccessPolicyResponse(inputStream)); + } + + /// + /// Reads share stats from a stream. + /// + /// The stream from which to read the share stats. + /// The share stats stored in the stream. + public static ShareStats ReadShareStats(Stream inputStream) + { + using (XmlReader reader = XmlReader.Create(inputStream)) + { + XDocument shareStatsDocument = XDocument.Load(reader); + + return ShareStats.FromServiceXml(shareStatsDocument); + } + } } } diff --git a/Lib/WindowsRuntime/Microsoft.WindowsAzure.Storage.csproj b/Lib/WindowsRuntime/Microsoft.WindowsAzure.Storage.csproj index fcd170f26..a623a1575 100644 --- a/Lib/WindowsRuntime/Microsoft.WindowsAzure.Storage.csproj +++ b/Lib/WindowsRuntime/Microsoft.WindowsAzure.Storage.csproj @@ -60,6 +60,7 @@ + diff --git a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs index 70a9f7ac8..2541e7bb4 100644 --- a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs @@ -25,9 +25,8 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.4.1.0")] -[assembly: AssemblyFileVersion("4.4.1.0")] -[assembly: AssemblyInformationalVersion("4.4.1.0-preview")] +[assembly: AssemblyVersion("5.0.0.0")] +[assembly: AssemblyFileVersion("5.0.0.0")] [assembly: ComVisible(false)] #if SIGN diff --git a/Lib/WindowsRuntime/Queue/CloudQueue.cs b/Lib/WindowsRuntime/Queue/CloudQueue.cs index 337e06f47..6bc1bd936 100644 --- a/Lib/WindowsRuntime/Queue/CloudQueue.cs +++ b/Lib/WindowsRuntime/Queue/CloudQueue.cs @@ -46,7 +46,7 @@ public sealed partial class CloudQueue /// Creates the queue. /// #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync() #else @@ -64,7 +64,7 @@ public IAsyncAction CreateAsync() /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync(QueueRequestOptions options, OperationContext operationContext) { @@ -93,7 +93,7 @@ public IAsyncAction CreateAsync(QueueRequestOptions options, OperationContext op /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync(QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -211,7 +211,7 @@ public IAsyncOperation CreateIfNotExistsAsync(QueueRequestOptions options, /// Deletes the queue. /// #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteAsync() #else @@ -229,7 +229,7 @@ public IAsyncAction DeleteAsync() /// An object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteAsync(QueueRequestOptions options, OperationContext operationContext) { @@ -258,7 +258,7 @@ public IAsyncAction DeleteAsync(QueueRequestOptions options, OperationContext op /// An object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteAsync(QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -372,7 +372,7 @@ public IAsyncOperation DeleteIfExistsAsync(QueueRequestOptions options, Op /// /// The permissions to apply to the queue. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetPermissionsAsync(QueuePermissions permissions) #else @@ -391,7 +391,7 @@ public IAsyncAction SetPermissionsAsync(QueuePermissions permissions) /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetPermissionsAsync(QueuePermissions permissions, QueueRequestOptions options, OperationContext operationContext) { @@ -421,7 +421,7 @@ public IAsyncAction SetPermissionsAsync(QueuePermissions permissions, QueueReque /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetPermissionsAsync(QueuePermissions permissions, QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -586,7 +586,7 @@ private IAsyncOperation ExistsAsync(bool primaryOnly, QueueRequestOptions /// Retrieves the queue's attributes. /// #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task FetchAttributesAsync() #else @@ -604,7 +604,7 @@ public IAsyncAction FetchAttributesAsync() /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task FetchAttributesAsync(QueueRequestOptions options, OperationContext operationContext) { @@ -633,7 +633,7 @@ public IAsyncAction FetchAttributesAsync(QueueRequestOptions options, OperationC /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task FetchAttributesAsync(QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -653,7 +653,7 @@ public Task FetchAttributesAsync(QueueRequestOptions options, OperationContext o /// Sets the queue's user-defined metadata. /// #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetMetadataAsync() #else @@ -671,7 +671,7 @@ public IAsyncAction SetMetadataAsync() /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetMetadataAsync(QueueRequestOptions options, OperationContext operationContext) { @@ -700,7 +700,7 @@ public IAsyncAction SetMetadataAsync(QueueRequestOptions options, OperationConte /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetMetadataAsync(QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -721,7 +721,7 @@ public Task SetMetadataAsync(QueueRequestOptions options, OperationContext opera /// /// The message to add. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task AddMessageAsync(CloudQueueMessage message) #else @@ -743,7 +743,7 @@ public IAsyncAction AddMessageAsync(CloudQueueMessage message) /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task AddMessageAsync(CloudQueueMessage message, TimeSpan? timeToLive, TimeSpan? initialVisibilityDelay, QueueRequestOptions options, OperationContext operationContext) { @@ -776,7 +776,7 @@ public IAsyncAction AddMessageAsync(CloudQueueMessage message, TimeSpan? timeToL /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task AddMessageAsync(CloudQueueMessage message, TimeSpan? timeToLive, TimeSpan? initialVisibilityDelay, QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -798,7 +798,7 @@ public Task AddMessageAsync(CloudQueueMessage message, TimeSpan? timeToLive, Tim /// The visibility timeout interval. /// The message update fields. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UpdateMessageAsync(CloudQueueMessage message, TimeSpan visibilityTimeout, MessageUpdateFields updateFields) #else @@ -819,7 +819,7 @@ public IAsyncAction UpdateMessageAsync(CloudQueueMessage message, TimeSpan visib /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UpdateMessageAsync(CloudQueueMessage message, TimeSpan visibilityTimeout, MessageUpdateFields updateFields, QueueRequestOptions options, OperationContext operationContext) { @@ -851,7 +851,7 @@ public IAsyncAction UpdateMessageAsync(CloudQueueMessage message, TimeSpan visib /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task UpdateMessageAsync(CloudQueueMessage message, TimeSpan visibilityTimeout, MessageUpdateFields updateFields, QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -871,7 +871,7 @@ public Task UpdateMessageAsync(CloudQueueMessage message, TimeSpan visibilityTim /// /// The message to delete. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteMessageAsync(CloudQueueMessage message) #else @@ -890,7 +890,7 @@ public IAsyncAction DeleteMessageAsync(CloudQueueMessage message) /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteMessageAsync(CloudQueueMessage message, QueueRequestOptions options, OperationContext operationContext) #else @@ -908,7 +908,7 @@ public IAsyncAction DeleteMessageAsync(CloudQueueMessage message, QueueRequestOp /// The message ID. /// The pop receipt value. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteMessageAsync(string messageId, string popReceipt) #else @@ -928,7 +928,7 @@ public IAsyncAction DeleteMessageAsync(string messageId, string popReceipt) /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteMessageAsync(string messageId, string popReceipt, QueueRequestOptions options, OperationContext operationContext) { @@ -959,7 +959,7 @@ public IAsyncAction DeleteMessageAsync(string messageId, string popReceipt, Queu /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteMessageAsync(string messageId, string popReceipt, QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -979,7 +979,7 @@ public Task DeleteMessageAsync(string messageId, string popReceipt, QueueRequest /// /// The number of messages to retrieve. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task> GetMessagesAsync(int messageCount) #else @@ -1112,7 +1112,7 @@ public Task GetMessageAsync(TimeSpan? visibilityTimeout, Queu /// /// The number of messages to retrieve. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task> PeekMessagesAsync(int messageCount) #else @@ -1240,7 +1240,7 @@ public Task PeekMessageAsync(QueueRequestOptions options, Ope /// Clears the messages of the queue. /// #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ClearAsync() #else @@ -1258,7 +1258,7 @@ public IAsyncAction ClearAsync() /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. #if ASPNET_K || PORTABLE - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ClearAsync(QueueRequestOptions options, OperationContext operationContext) { @@ -1287,7 +1287,7 @@ public IAsyncAction ClearAsync(QueueRequestOptions options, OperationContext ope /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task ClearAsync(QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) { @@ -1472,6 +1472,7 @@ private RESTCommand SetPermissionsImpl(QueuePermissions acl, QueueRequ putCmd.BuildClient = HttpClientFactory.BuildHttpClient; putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => QueueHttpRequestMessageFactory.SetAcl(uri, serverTimeout, cnt, ctx); putCmd.BuildContent = (cmd, ctx) => HttpContentFactory.BuildContentFromStream(memoryStream, 0, memoryStream.Length, null /* md5 */, cmd, ctx); + putCmd.StreamToDispose = memoryStream; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.NoContent, resp, NullType.Value, cmd, ex); @@ -1549,6 +1550,7 @@ private RESTCommand AddMessageImpl(CloudQueueMessage message, TimeSpan putCmd.BuildClient = HttpClientFactory.BuildHttpClient; putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => QueueHttpRequestMessageFactory.AddMessage(uri, serverTimeout, timeToLiveInSeconds, initialVisibilityDelayInSeconds, cnt, ctx); putCmd.BuildContent = (cmd, ctx) => HttpContentFactory.BuildContentFromStream(memoryStream, 0, memoryStream.Length, null, cmd, ctx); + putCmd.StreamToDispose = memoryStream; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.Created, resp, NullType.Value, cmd, ex); @@ -1594,6 +1596,7 @@ private RESTCommand UpdateMessageImpl(CloudQueueMessage message, TimeS memoryStream.Seek(0, SeekOrigin.Begin); putCmd.BuildContent = (cmd, ctx) => HttpContentFactory.BuildContentFromStream(memoryStream, 0, memoryStream.Length, null, cmd, ctx); + putCmd.StreamToDispose = memoryStream; } putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => diff --git a/Lib/WindowsRuntime/Queue/CloudQueueClient.cs b/Lib/WindowsRuntime/Queue/CloudQueueClient.cs index 28c2f222a..c3b98ebe4 100644 --- a/Lib/WindowsRuntime/Queue/CloudQueueClient.cs +++ b/Lib/WindowsRuntime/Queue/CloudQueueClient.cs @@ -401,6 +401,7 @@ private RESTCommand SetServicePropertiesImpl(ServiceProperties propert requestOptions.ApplyToStorageCommand(retCmd); retCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => QueueHttpRequestMessageFactory.SetServiceProperties(uri, serverTimeout, cnt, ctx); retCmd.BuildContent = (cmd, ctx) => HttpContentFactory.BuildContentFromStream(memoryStream, 0, memoryStream.Length, null /* md5 */, cmd, ctx); + retCmd.StreamToDispose = memoryStream; retCmd.Handler = this.AuthenticationHandler; retCmd.BuildClient = HttpClientFactory.BuildHttpClient; retCmd.PreProcessResponse = diff --git a/Lib/WindowsRuntime/Shared/Protocol/HttpContentFactory.cs b/Lib/WindowsRuntime/Shared/Protocol/HttpContentFactory.cs index 210c2124b..f461aa008 100644 --- a/Lib/WindowsRuntime/Shared/Protocol/HttpContentFactory.cs +++ b/Lib/WindowsRuntime/Shared/Protocol/HttpContentFactory.cs @@ -38,11 +38,6 @@ public static HttpContent BuildContentFromStream(Stream stream, long offset, } #endif - if (stream is MultiBufferMemoryStream) - { - cmd.StreamToDispose = stream; - } - return retContent; } } diff --git a/Lib/WindowsRuntime/Shared/Protocol/RequestMessageExtensions.cs b/Lib/WindowsRuntime/Shared/Protocol/RequestMessageExtensions.cs index 41161a6b0..79298c9f2 100644 --- a/Lib/WindowsRuntime/Shared/Protocol/RequestMessageExtensions.cs +++ b/Lib/WindowsRuntime/Shared/Protocol/RequestMessageExtensions.cs @@ -144,6 +144,20 @@ internal static void ApplyAccessCondition(this HttpRequestMessage request, Acces } } + /// + /// Applies the append condition to the web request. + /// + /// The request to be modified. + /// Access condition to be added to the request. + internal static void ApplyAppendCondition(this HttpRequestMessage request, AccessCondition accessCondition) + { + if (accessCondition != null) + { + request.AddOptionalHeader(Constants.HeaderConstants.IfMaxSizeLessThanOrEqualHeader, accessCondition.IfMaxSizeLessThanOrEqual); + request.AddOptionalHeader(Constants.HeaderConstants.IfAppendPositionEqualHeader, accessCondition.IfAppendPositionEqual); + } + } + /// /// Applies the condition for a source blob to the web request. /// diff --git a/Lib/WindowsRuntime/Table/CloudTable.cs b/Lib/WindowsRuntime/Table/CloudTable.cs index 5ccb8787f..e70f1ffce 100644 --- a/Lib/WindowsRuntime/Table/CloudTable.cs +++ b/Lib/WindowsRuntime/Table/CloudTable.cs @@ -219,7 +219,7 @@ public Task ExecuteQuerySegmentedAsync(TableQuery query, Tabl /// Creates the Table. /// #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. public Task CreateAsync() #else /// An that represents an asynchronous action. @@ -235,7 +235,7 @@ public IAsyncAction CreateAsync() /// A object that specifies additional options for the request. /// An object for tracking the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. public Task CreateAsync(TableRequestOptions requestOptions, OperationContext operationContext) { return this.CreateAsync(requestOptions, operationContext, CancellationToken.None); @@ -247,7 +247,7 @@ public Task CreateAsync(TableRequestOptions requestOptions, OperationContext ope /// A object that specifies additional options for the request. /// An object for tracking the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. public Task CreateAsync(TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) #else /// An that represents an asynchronous action. @@ -371,7 +371,7 @@ public IAsyncOperation CreateIfNotExistsAsync(TableRequestOptions requestO /// Deletes the Table. /// #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. public Task DeleteAsync() #else /// An that represents an asynchronous action. @@ -387,7 +387,7 @@ public IAsyncAction DeleteAsync() /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object for tracking the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. public Task DeleteAsync(TableRequestOptions requestOptions, OperationContext operationContext) { return this.DeleteAsync(requestOptions, operationContext, CancellationToken.None); @@ -399,7 +399,7 @@ public Task DeleteAsync(TableRequestOptions requestOptions, OperationContext ope /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object for tracking the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. public Task DeleteAsync(TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) #else /// An that represents an asynchronous action. @@ -614,7 +614,7 @@ private IAsyncOperation ExistsAsync(bool primaryOnly, TableRequestOptions /// /// The permissions to apply to the Table. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. public Task SetPermissionsAsync(TablePermissions permissions) #else /// An that represents an asynchronous action. @@ -658,7 +658,7 @@ public IAsyncAction SetPermissionsAsync(TablePermissions permissions, TableReque /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object for tracking the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. public Task SetPermissionsAsync(TablePermissions permissions, TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) { TableRequestOptions modifiedOptions = TableRequestOptions.ApplyDefaults(requestOptions, this.ServiceClient); @@ -690,6 +690,7 @@ private RESTCommand SetPermissionsImpl(TablePermissions acl, TableRequ putCmd.BuildClient = HttpClientFactory.BuildHttpClient; putCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableHttpRequestMessageFactory.SetAcl(uri, serverTimeout, cnt, ctx); putCmd.BuildContent = (cmd, ctx) => HttpContentFactory.BuildContentFromStream(memoryStream, 0, memoryStream.Length, null /* md5 */, cmd, ctx); + putCmd.StreamToDispose = memoryStream; putCmd.PreProcessResponse = (cmd, resp, ex, ctx) => { HttpResponseParsers.ProcessExpectedStatusCodeNoException(HttpStatusCode.NoContent, resp, NullType.Value, cmd, ex); diff --git a/Lib/WindowsRuntime/Table/CloudTableClient.cs b/Lib/WindowsRuntime/Table/CloudTableClient.cs index 6b357f648..cbb5246cd 100644 --- a/Lib/WindowsRuntime/Table/CloudTableClient.cs +++ b/Lib/WindowsRuntime/Table/CloudTableClient.cs @@ -352,7 +352,7 @@ private RESTCommand GetServicePropertiesImpl(TableRequestOpti /// The table service properties. [DoesServiceRequest] #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. public Task SetServicePropertiesAsync(ServiceProperties properties) #else /// An that represents an asynchronous action. @@ -369,7 +369,7 @@ public IAsyncAction SetServicePropertiesAsync(ServiceProperties properties) /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object for tracking the current operation. #if ASPNET_K - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetServicePropertiesAsync(ServiceProperties properties, TableRequestOptions requestOptions, OperationContext operationContext) { @@ -398,7 +398,7 @@ public IAsyncAction SetServicePropertiesAsync(ServiceProperties properties, Tabl /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object for tracking the current operation. /// A to observe while waiting for a task to complete. - /// An that represents an asynchronous action. + /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetServicePropertiesAsync(ServiceProperties properties, TableRequestOptions requestOptions, OperationContext operationContext, CancellationToken cancellationToken) { @@ -428,6 +428,7 @@ private RESTCommand SetServicePropertiesImpl(ServiceProperties propert requestOptions.ApplyToStorageCommand(retCmd); retCmd.BuildRequest = (cmd, uri, builder, cnt, serverTimeout, ctx) => TableHttpRequestMessageFactory.SetServiceProperties(uri, serverTimeout, cnt, ctx); retCmd.BuildContent = (cmd, ctx) => HttpContentFactory.BuildContentFromStream(memoryStream, 0, memoryStream.Length, null /* md5 */, cmd, ctx); + retCmd.StreamToDispose = memoryStream; retCmd.Handler = this.AuthenticationHandler; retCmd.BuildClient = HttpClientFactory.BuildHttpClient; retCmd.PreProcessResponse = diff --git a/Lib/WindowsRuntime/Table/TableOperation.cs b/Lib/WindowsRuntime/Table/TableOperation.cs index f4822a27b..670fe9592 100644 --- a/Lib/WindowsRuntime/Table/TableOperation.cs +++ b/Lib/WindowsRuntime/Table/TableOperation.cs @@ -111,7 +111,6 @@ internal IAsyncOperation ExecuteAsync(CloudTableClient client, stri operationContext, cancellationToken)); #endif - } private static RESTCommand InsertImpl(TableOperation operation, CloudTableClient client, string tableName, TableRequestOptions requestOptions) diff --git a/Lib/WindowsRuntime/packages.config b/Lib/WindowsRuntime/packages.config index cc5407b76..39fae7f18 100644 --- a/Lib/WindowsRuntime/packages.config +++ b/Lib/WindowsRuntime/packages.config @@ -1,6 +1,6 @@  - - - + + + \ No newline at end of file diff --git a/Nuget.config b/Nuget.config index 038674cb5..53454b200 100644 --- a/Nuget.config +++ b/Nuget.config @@ -1,4 +1,4 @@ - + diff --git a/README.md b/README.md index 6f50cb22f..128b06538 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Microsoft Azure Storage SDK for .NET (4.4.1-Preview) +# Microsoft Azure Storage SDK for .NET (5.0.0) The Microsoft Azure Storage SDK for .NET allows you to build Azure applications that take advantage of scalable cloud computing resources. @@ -45,7 +45,7 @@ For the best development experience, developers should use the official Microsof The Storage Client Library ships with the Microsoft Azure SDK for .NET and also on NuGet. You'll find the latest version and hotfixes on NuGet via the `WindowsAzure.Storage` package. -This version of the Storage Client Library ships with the new storage version 2014-02-14. This storage version provides a preview of the Microsoft Azure File Service. For more details, +This version of the Storage Client Library ships with the storage version 2015-02-21. This storage version provides a preview of the Microsoft Azure File Service. For more details, please see the [Introducing Microsoft Azure File Service blog on MSDN] (http://blogs.msdn.com/b/windowsazurestorage/archive/2014/05/11/introducing-microsoft-azure-file-service.aspx). ### Via Git @@ -68,7 +68,7 @@ within your project you can also have them installed by the .NET package manager ### OData -This version depends on three libraries (collectively referred to as ODataLib), which are resolved through the ODataLib (version 5.6.2) packages available through NuGet and not the WCF Data Services installer which currently contains 5.0.0 versions. +This version depends on three libraries (collectively referred to as ODataLib), which are resolved through the ODataLib (version 5.6.4) packages available through NuGet and not the WCF Data Services installer which currently contains 5.0.0 versions. The ODataLib libraries can be downloaded directly or referenced by your code project through NuGet. @@ -92,7 +92,7 @@ The desktop library depends on WCF Data Services Client, which can be downloaded ### Key Vault -The client-side encryption support, currently in preview, depends on the KeyVault.Core package, which can be downloaded directly or referenced by your code project through Nuget. +The client-side encryption support depends on the KeyVault.Core package, which can be downloaded directly or referenced by your code project through Nuget. - [KeyVault.Core] (http://www.nuget.org/packages/Microsoft.Azure.KeyVault.Core) diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/App.config b/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/App.config new file mode 100644 index 000000000..f6da346b3 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/App.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/BlobGettingStarted.csproj b/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/BlobGettingStarted.csproj new file mode 100644 index 000000000..dcb4b211b --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/BlobGettingStarted.csproj @@ -0,0 +1,140 @@ + + + + + Debug + AnyCPU + {6DFB68F9-ECAC-4105-95A7-0A51B840A691} + Exe + Properties + BlobGettingStarted + BlobGettingStarted + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Hyak.Common.1.0.2\lib\net45\Hyak.Common.dll + True + + + ..\packages\Microsoft.Azure.Common.2.0.4\lib\net45\Microsoft.Azure.Common.dll + True + + + ..\packages\Microsoft.Azure.Common.2.0.4\lib\net45\Microsoft.Azure.Common.NetFramework.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.0.9.1-preview\lib\net45\Microsoft.Azure.KeyVault.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.Core.0.9.1-preview\lib\net40\Microsoft.Azure.KeyVault.Core.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.Extensions.0.9.1-preview\lib\net45\Microsoft.Azure.KeyVault.Extensions.dll + True + + + ..\packages\Microsoft.Data.Edm.5.6.2\lib\net40\Microsoft.Data.Edm.dll + True + + + ..\packages\Microsoft.Data.OData.5.6.2\lib\net40\Microsoft.Data.OData.dll + True + + + ..\packages\Microsoft.Data.Services.Client.5.6.2\lib\net40\Microsoft.Data.Services.Client.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll + True + + + ..\packages\Microsoft.WindowsAzure.ConfigurationManager.1.8.0.0\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll + True + + + ..\packages\WindowsAzure.Storage.4.4.0-preview\lib\net40\Microsoft.WindowsAzure.Storage.dll + True + + + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + True + + + + + + + ..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Extensions.dll + True + + + ..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Primitives.dll + True + + + + ..\packages\System.Spatial.5.6.2\lib\net40\System.Spatial.dll + True + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/LocalResolver.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/LocalResolver.cs new file mode 100644 index 000000000..2d3a03b3e --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/LocalResolver.cs @@ -0,0 +1,42 @@ +//---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- +namespace BlobGettingStarted +{ + using Microsoft.Azure.KeyVault.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + public class LocalResolver : IKeyResolver + { + private Dictionary keys = new Dictionary(); + + public void Add(IKey key) + { + keys[key.Kid] = key; + } + + public async Task ResolveKeyAsync(string kid, CancellationToken token) + { + IKey result; + + keys.TryGetValue(kid, out result); + + return await Task.FromResult(result); + } + } +} diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/Program.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/Program.cs new file mode 100644 index 000000000..98cb46acc --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/Program.cs @@ -0,0 +1,122 @@ +//---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- +namespace BlobGettingStarted +{ + using Microsoft.Azure.KeyVault; + using Microsoft.Azure.KeyVault.Core; + using Microsoft.WindowsAzure; + using Microsoft.WindowsAzure.Storage; + using Microsoft.WindowsAzure.Storage.Blob; + using System; + using System.IO; + using System.Security.Cryptography; + + /// + /// Demonstrates how to use encryption with the Azure Blob service. + /// + public class Program + { + const string DemoContainer = "democontainer"; + + static void Main(string[] args) + { + Console.WriteLine("Blob encryption sample"); + + // Retrieve storage account information from connection string + // How to create a storage connection string - http://msdn.microsoft.com/en-us/library/azure/ee758697.aspx + CloudStorageAccount storageAccount = CreateStorageAccountFromConnectionString(CloudConfigurationManager.GetSetting("StorageConnectionString")); + CloudBlobClient client = storageAccount.CreateCloudBlobClient(); + CloudBlobContainer container = client.GetContainerReference(DemoContainer + Guid.NewGuid().ToString("N")); + + try + { + container.Create(); + int size = 5 * 1024 * 1024; + byte[] buffer = new byte[size]; + + Random rand = new Random(); + rand.NextBytes(buffer); + + CloudBlockBlob blob = container.GetBlockBlobReference("blockblob"); + + // Create the IKey used for encryption. + RsaKey key = new RsaKey("private:key1"); + + // Create the encryption policy to be used for upload. + BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(key, null); + + // Set the encryption policy on the request options. + BlobRequestOptions uploadOptions = new BlobRequestOptions() { EncryptionPolicy = uploadPolicy }; + + Console.WriteLine("Uploading the encrypted blob."); + + // Upload the encrypted contents to the blob. + using (MemoryStream stream = new MemoryStream(buffer)) + { + blob.UploadFromStream(stream, size, null, uploadOptions, null); + } + + // Download the encrypted blob. + // For downloads, a resolver can be set up that will help pick the key based on the key id. + LocalResolver resolver = new LocalResolver(); + resolver.Add(key); + + BlobEncryptionPolicy downloadPolicy = new BlobEncryptionPolicy(null, resolver); + + // Set the decryption policy on the request options. + BlobRequestOptions downloadOptions = new BlobRequestOptions() { EncryptionPolicy = downloadPolicy }; + + Console.WriteLine("Downloading the encrypted blob."); + + // Download and decrypt the encrypted contents from the blob. + using (MemoryStream outputStream = new MemoryStream()) + { + blob.DownloadToStream(outputStream, null, downloadOptions, null); + } + + Console.WriteLine("Press enter key to exit"); + Console.ReadLine(); + } + finally + { + container.DeleteIfExists(); + } + } + + private static CloudStorageAccount CreateStorageAccountFromConnectionString(string storageConnectionString) + { + CloudStorageAccount storageAccount; + try + { + storageAccount = CloudStorageAccount.Parse(storageConnectionString); + } + catch (FormatException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + throw; + } + catch (ArgumentException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + throw; + } + + return storageAccount; + } + } +} \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/Properties/AssemblyInfo.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..0d4b4b6d0 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("BlobGettingStarted")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("BlobGettingStarted")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("66d06292-516c-41b4-9041-aa94b8115119")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/packages.config b/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/packages.config new file mode 100644 index 000000000..2133f779f --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/BlobGettingStarted/packages.config @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/GettingStartedSamples.sln b/Samples/GettingStarted/DotNet/EncryptionSamples/GettingStartedSamples.sln new file mode 100644 index 000000000..91d872789 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/GettingStartedSamples.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QueueGettingStarted", "QueueGettingStarted\QueueGettingStarted.csproj", "{96209F76-E19B-426E-8C02-6CE26B6187C9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TableGettingStartedUsingResolver", "TableGettingStarted\TableGettingStartedUsingResolver.csproj", "{449D75A7-140A-495A-B35A-9B056095F571}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlobGettingStarted", "BlobGettingStarted\BlobGettingStarted.csproj", "{6DFB68F9-ECAC-4105-95A7-0A51B840A691}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KVGettingStarted", "KVGettingStarted\KVGettingStarted.csproj", "{AB26C6E1-1375-45FD-BF32-4FAE7FFF078A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TableGettingStartedUsingAttributes", "TableGettingStartedUsingAttributes\TableGettingStartedUsingAttributes.csproj", "{804DF8D6-CCCC-4408-8B2E-2FF09CC3B266}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {96209F76-E19B-426E-8C02-6CE26B6187C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96209F76-E19B-426E-8C02-6CE26B6187C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96209F76-E19B-426E-8C02-6CE26B6187C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96209F76-E19B-426E-8C02-6CE26B6187C9}.Release|Any CPU.Build.0 = Release|Any CPU + {449D75A7-140A-495A-B35A-9B056095F571}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {449D75A7-140A-495A-B35A-9B056095F571}.Debug|Any CPU.Build.0 = Debug|Any CPU + {449D75A7-140A-495A-B35A-9B056095F571}.Release|Any CPU.ActiveCfg = Release|Any CPU + {449D75A7-140A-495A-B35A-9B056095F571}.Release|Any CPU.Build.0 = Release|Any CPU + {6DFB68F9-ECAC-4105-95A7-0A51B840A691}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DFB68F9-ECAC-4105-95A7-0A51B840A691}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DFB68F9-ECAC-4105-95A7-0A51B840A691}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DFB68F9-ECAC-4105-95A7-0A51B840A691}.Release|Any CPU.Build.0 = Release|Any CPU + {AB26C6E1-1375-45FD-BF32-4FAE7FFF078A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB26C6E1-1375-45FD-BF32-4FAE7FFF078A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB26C6E1-1375-45FD-BF32-4FAE7FFF078A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB26C6E1-1375-45FD-BF32-4FAE7FFF078A}.Release|Any CPU.Build.0 = Release|Any CPU + {804DF8D6-CCCC-4408-8B2E-2FF09CC3B266}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {804DF8D6-CCCC-4408-8B2E-2FF09CC3B266}.Debug|Any CPU.Build.0 = Debug|Any CPU + {804DF8D6-CCCC-4408-8B2E-2FF09CC3B266}.Release|Any CPU.ActiveCfg = Release|Any CPU + {804DF8D6-CCCC-4408-8B2E-2FF09CC3B266}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/App.config b/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/App.config new file mode 100644 index 000000000..dedb150a2 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/App.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/KVGettingStarted.csproj b/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/KVGettingStarted.csproj new file mode 100644 index 000000000..fe4b7d00f --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/KVGettingStarted.csproj @@ -0,0 +1,148 @@ + + + + + Debug + AnyCPU + {AB26C6E1-1375-45FD-BF32-4FAE7FFF078A} + Exe + Properties + KVGettingStarted + KVGettingStarted + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Hyak.Common.1.0.2\lib\net45\Hyak.Common.dll + True + + + ..\packages\Microsoft.Azure.Common.2.0.4\lib\net45\Microsoft.Azure.Common.dll + True + + + ..\packages\Microsoft.Azure.Common.2.0.4\lib\net45\Microsoft.Azure.Common.NetFramework.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.0.9.1-preview\lib\net45\Microsoft.Azure.KeyVault.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.Core.0.9.1-preview\lib\net40\Microsoft.Azure.KeyVault.Core.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.Extensions.0.9.1-preview\lib\net45\Microsoft.Azure.KeyVault.Extensions.dll + True + + + ..\packages\Microsoft.Data.Edm.5.6.2\lib\net40\Microsoft.Data.Edm.dll + True + + + ..\packages\Microsoft.Data.OData.5.6.2\lib\net40\Microsoft.Data.OData.dll + True + + + ..\packages\Microsoft.Data.Services.Client.5.6.2\lib\net40\Microsoft.Data.Services.Client.dll + True + + + ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.16.204221202\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + True + + + ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.16.204221202\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll + True + + + ..\packages\Microsoft.WindowsAzure.ConfigurationManager.1.8.0.0\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll + True + + + ..\packages\WindowsAzure.Storage.4.4.0-preview\lib\net40\Microsoft.WindowsAzure.Storage.dll + True + + + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + True + + + + + + + ..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Extensions.dll + True + + + ..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Primitives.dll + True + + + + ..\packages\System.Spatial.5.6.2\lib\net40\System.Spatial.dll + True + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/LocalResolver.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/LocalResolver.cs new file mode 100644 index 000000000..27c34063b --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/LocalResolver.cs @@ -0,0 +1,42 @@ +//---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- +namespace KVGettingStarted +{ + using Microsoft.Azure.KeyVault.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + public class LocalResolver : IKeyResolver + { + private Dictionary keys = new Dictionary(); + + public void Add(IKey key) + { + keys[key.Kid] = key; + } + + public async Task ResolveKeyAsync(string kid, CancellationToken token) + { + IKey result; + + keys.TryGetValue(kid, out result); + + return await Task.FromResult(result); + } + } +} diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/Program.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/Program.cs new file mode 100644 index 000000000..cb8ca10bd --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/Program.cs @@ -0,0 +1,189 @@ +using Microsoft.Azure.KeyVault; +using Microsoft.Azure.KeyVault.Core; +using Microsoft.IdentityModel.Clients.ActiveDirectory; +using Microsoft.WindowsAzure; +using Microsoft.WindowsAzure.Storage; +using Microsoft.WindowsAzure.Storage.Blob; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace KVGettingStarted +{ + /// + /// Demonstrates how to use encryption along with Azure Key Vault integration for the Azure Blob service. + /// + public class Program + { + const string DemoContainer = "democontainer"; + + static async Task GetAccessToken(string authority, string resource, string scope) + { + ClientCredential credential = new ClientCredential(CloudConfigurationManager.GetSetting("KVClientId"), CloudConfigurationManager.GetSetting("KVClientKey")); + + AuthenticationContext ctx = new AuthenticationContext(new Uri(authority).AbsoluteUri, false); + AuthenticationResult result = await ctx.AcquireTokenAsync(resource, credential); + + return result.AccessToken; + } + + static void Main(string[] args) + { + Console.WriteLine("Blob encryption with Key Vault integration"); + + // Retrieve storage account information from connection string + // How to create a storage connection string - http://msdn.microsoft.com/en-us/library/azure/ee758697.aspx + CloudStorageAccount storageAccount = CreateStorageAccountFromConnectionString(CloudConfigurationManager.GetSetting("StorageConnectionString")); + CloudBlobClient client = storageAccount.CreateCloudBlobClient(); + CloudBlobContainer container = client.GetContainerReference(DemoContainer + Guid.NewGuid().ToString("N")); + + // Get reference to a Cloud Key Vault and key resolver. + KeyVaultClient cloudVault = new KeyVaultClient(GetAccessToken); + KeyVaultKeyResolver cloudResolver = new KeyVaultKeyResolver(GetAccessToken); + + // Get reference to a local key and key resolver. + RsaKey rsaKey = new RsaKey("rsakey"); + LocalResolver resolver = new LocalResolver(); + resolver.Add(rsaKey); + + // If there are multiple key sources like Azure Key Vault and local KMS, set up an aggregate resolver as follows. + // This helps users to define a plugin model for all the different key providers they support. + AggregateKeyResolver aggregateResolver = new AggregateKeyResolver() + .Add(resolver) + .Add(cloudResolver); + + // Set up a caching resolver so the secrets can be cached on the client. This is the recommended usage pattern since the throttling + // targets for Storage and Key Vault services are orders of magnitude different. + CachingKeyResolver cachingResolver = new CachingKeyResolver(2, aggregateResolver); + + // Establish a symmetric KEK stored as a Secret in the cloud key vault + string vaultUri = CloudConfigurationManager.GetSetting("VaultUri"); + + try + { + cloudVault.DeleteSecretAsync(vaultUri, "secret").GetAwaiter().GetResult(); + } + catch (KeyVaultClientException ex) + { + if (ex.Status != System.Net.HttpStatusCode.NotFound) + throw; + } + + // Create a symmetric 256bit symmetric key and convert it to Base64 + SymmetricKey symmetricKey = new SymmetricKey("secret", SymmetricKey.KeySize256); + string symmetricBytes = Convert.ToBase64String(symmetricKey.Key); + + // Store the Base64 of the key in the key vault. This is shown inline for simplicity but + // the recommended approach is to create this key offline and upload it to key vault and + // then use secrets base identifier as a parameter to resolve the current version of the + // secret for encryption. Note that the content-type of the secret must + // be application/octet-stream or the KeyVaultKeyResolver will refuse to load it as a key. + Secret cloudSecret = cloudVault.SetSecretAsync(vaultUri, "secret", symmetricBytes, null, "application/octet-stream").GetAwaiter().GetResult(); + IKey cloudKey = cachingResolver.ResolveKeyAsync(cloudSecret.SecretIdentifier.BaseIdentifier, CancellationToken.None).GetAwaiter().GetResult(); + + try + { + container.Create(); + int size = 5 * 1024 * 1024; + byte[] buffer = new byte[size]; + + Random rand = new Random(); + rand.NextBytes(buffer); + + // Upload first blob using the secret stored in Azure Key Vault. + CloudBlockBlob blob = container.GetBlockBlobReference("blockblob1"); + + // Create the encryption policy using the secret stored in Azure Key Vault to be used for upload. + BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(cloudKey, null); + + // Set the encryption policy on the request options. + BlobRequestOptions uploadOptions = new BlobRequestOptions() { EncryptionPolicy = uploadPolicy }; + + Console.WriteLine("Uploading the 1st encrypted blob."); + + // Upload the encrypted contents to the blob. + using (MemoryStream stream = new MemoryStream(buffer)) + { + blob.UploadFromStream(stream, size, null, uploadOptions, null); + } + + // Download the encrypted blob. + BlobEncryptionPolicy downloadPolicy = new BlobEncryptionPolicy(null, cachingResolver); + + // Set the decryption policy on the request options. + BlobRequestOptions downloadOptions = new BlobRequestOptions() { EncryptionPolicy = downloadPolicy }; + + Console.WriteLine("Downloading the 1st encrypted blob."); + + // Download and decrypt the encrypted contents from the blob. + using (MemoryStream outputStream = new MemoryStream()) + { + blob.DownloadToStream(outputStream, null, downloadOptions, null); + } + + // Upload second blob using the local key. + blob = container.GetBlockBlobReference("blockblob2"); + + // Create the encryption policy using the local key. + uploadPolicy = new BlobEncryptionPolicy(rsaKey, null); + + // Set the encryption policy on the request options. + uploadOptions = new BlobRequestOptions() { EncryptionPolicy = uploadPolicy }; + + Console.WriteLine("Uploading the 2nd encrypted blob."); + + // Upload the encrypted contents to the blob. + using (MemoryStream stream = new MemoryStream(buffer)) + { + blob.UploadFromStream(stream, size, null, uploadOptions, null); + } + + // Download the encrypted blob. The same policy and options created before can be used because the aggregate resolver contains both + // resolvers and will pick the right one based on the key id stored in blob metadata on the service. + Console.WriteLine("Downloading the 2nd encrypted blob."); + + // Download and decrypt the encrypted contents from the blob. + using (MemoryStream outputStream = new MemoryStream()) + { + blob.DownloadToStream(outputStream, null, downloadOptions, null); + } + + Console.WriteLine("Press enter key to exit"); + Console.ReadLine(); + } + finally + { + container.DeleteIfExists(); + } + } + + private static CloudStorageAccount CreateStorageAccountFromConnectionString(string storageConnectionString) + { + CloudStorageAccount storageAccount; + try + { + storageAccount = CloudStorageAccount.Parse(storageConnectionString); + } + catch (FormatException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + throw; + } + catch (ArgumentException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + throw; + } + + return storageAccount; + } + } +} diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/Properties/AssemblyInfo.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..91b9d360d --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("KVGettingStarted")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("KVGettingStarted")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("137f7827-64e9-4893-932a-3bb9a29909f3")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/packages.config b/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/packages.config new file mode 100644 index 000000000..618d3ced1 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/KVGettingStarted/packages.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/App.config b/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/App.config new file mode 100644 index 000000000..083ba01a6 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/App.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/LocalResolver.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/LocalResolver.cs new file mode 100644 index 000000000..97f86dc30 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/LocalResolver.cs @@ -0,0 +1,42 @@ +//---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- +namespace QueueGettingStarted +{ + using Microsoft.Azure.KeyVault.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + public class LocalResolver : IKeyResolver + { + private Dictionary keys = new Dictionary(); + + public void Add(IKey key) + { + keys[key.Kid] = key; + } + + public async Task ResolveKeyAsync(string kid, CancellationToken token) + { + IKey result; + + keys.TryGetValue(kid, out result); + + return await Task.FromResult(result); + } + } +} diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/Program.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/Program.cs new file mode 100644 index 000000000..e42cf55c3 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/Program.cs @@ -0,0 +1,117 @@ +//---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- +namespace QueueGettingStarted +{ + using Microsoft.Azure.KeyVault; + using Microsoft.WindowsAzure; + using Microsoft.WindowsAzure.Storage; + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Queue; + using System; + using System.IO; + using System.Security.Cryptography; + + /// + /// Demonstrates how to use encryption with the Azure Queue service. + /// + public class Program + { + const string DemoQueue = "demoqueue"; + + static void Main(string[] args) + { + Console.WriteLine("Queue encryption sample"); + + // Retrieve storage account information from connection string + // How to create a storage connection string - http://msdn.microsoft.com/en-us/library/azure/ee758697.aspx + CloudStorageAccount storageAccount = CreateStorageAccountFromConnectionString(CloudConfigurationManager.GetSetting("StorageConnectionString")); + CloudQueueClient client = storageAccount.CreateCloudQueueClient(); + CloudQueue queue = client.GetQueueReference(DemoQueue + Guid.NewGuid().ToString("N")); + + try + { + queue.Create(); + + // Create the IKey used for encryption. + RsaKey key = new RsaKey("private:key1"); + + // Create the encryption policy to be used for insert and update. + QueueEncryptionPolicy insertPolicy = new QueueEncryptionPolicy(key, null); + + // Set the encryption policy on the request options. + QueueRequestOptions insertOptions = new QueueRequestOptions() { EncryptionPolicy = insertPolicy }; + + string messageStr = Guid.NewGuid().ToString(); + CloudQueueMessage message = new CloudQueueMessage(messageStr); + + // Add message + Console.WriteLine("Inserting the encrypted message."); + queue.AddMessage(message, null, null, insertOptions, null); + + // For retrieves, a resolver can be set up that will help pick the key based on the key id. + LocalResolver resolver = new LocalResolver(); + resolver.Add(key); + + QueueEncryptionPolicy retrPolicy = new QueueEncryptionPolicy(null, resolver); + QueueRequestOptions retrieveOptions = new QueueRequestOptions() { EncryptionPolicy = retrPolicy }; + + // Retrieve message + Console.WriteLine("Retrieving the encrypted message."); + CloudQueueMessage retrMessage = queue.GetMessage(null, retrieveOptions, null); + + // Update message + Console.WriteLine("Updating the encrypted message."); + string updatedMessage = Guid.NewGuid().ToString("N"); + retrMessage.SetMessageContent(updatedMessage); + queue.UpdateMessage(retrMessage, TimeSpan.FromSeconds(0), MessageUpdateFields.Content | MessageUpdateFields.Visibility, insertOptions, null); + + // Retrieve updated message + Console.WriteLine("Retrieving the updated encrypted message."); + retrMessage = queue.GetMessage(null, retrieveOptions, null); + + Console.WriteLine("Press enter key to exit"); + Console.ReadLine(); + } + finally + { + queue.DeleteIfExists(); + } + } + + private static CloudStorageAccount CreateStorageAccountFromConnectionString(string storageConnectionString) + { + CloudStorageAccount storageAccount; + try + { + storageAccount = CloudStorageAccount.Parse(storageConnectionString); + } + catch (FormatException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + throw; + } + catch (ArgumentException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + throw; + } + + return storageAccount; + } + } +} \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/Properties/AssemblyInfo.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..ce5ce7026 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("QueueGettingStarted")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("QueueGettingStarted")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("88aebdfe-c738-41aa-9c66-6e52c0ac5e83")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/QueueGettingStarted.csproj b/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/QueueGettingStarted.csproj new file mode 100644 index 000000000..c2696fc4a --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/QueueGettingStarted.csproj @@ -0,0 +1,140 @@ + + + + + Debug + AnyCPU + {96209F76-E19B-426E-8C02-6CE26B6187C9} + Exe + Properties + QueueGettingStarted + QueueGettingStarted + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Hyak.Common.1.0.2\lib\net45\Hyak.Common.dll + True + + + ..\packages\Microsoft.Azure.Common.2.0.4\lib\net45\Microsoft.Azure.Common.dll + True + + + ..\packages\Microsoft.Azure.Common.2.0.4\lib\net45\Microsoft.Azure.Common.NetFramework.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.0.9.1-preview\lib\net45\Microsoft.Azure.KeyVault.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.Core.0.9.1-preview\lib\net40\Microsoft.Azure.KeyVault.Core.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.Extensions.0.9.1-preview\lib\net45\Microsoft.Azure.KeyVault.Extensions.dll + True + + + ..\packages\Microsoft.Data.Edm.5.6.2\lib\net40\Microsoft.Data.Edm.dll + True + + + ..\packages\Microsoft.Data.OData.5.6.2\lib\net40\Microsoft.Data.OData.dll + True + + + ..\packages\Microsoft.Data.Services.Client.5.6.2\lib\net40\Microsoft.Data.Services.Client.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll + True + + + ..\packages\Microsoft.WindowsAzure.ConfigurationManager.1.8.0.0\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll + True + + + ..\packages\WindowsAzure.Storage.4.4.0-preview\lib\net40\Microsoft.WindowsAzure.Storage.dll + True + + + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + True + + + + + + + ..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Extensions.dll + True + + + ..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Primitives.dll + True + + + + ..\packages\System.Spatial.5.6.2\lib\net40\System.Spatial.dll + True + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/packages.config b/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/packages.config new file mode 100644 index 000000000..2133f779f --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/QueueGettingStarted/packages.config @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/App.config b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/App.config new file mode 100644 index 000000000..083ba01a6 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/App.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/LocalResolver.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/LocalResolver.cs new file mode 100644 index 000000000..616a553e0 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/LocalResolver.cs @@ -0,0 +1,42 @@ +//---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- +namespace TableGettingStartedUsingResolver +{ + using Microsoft.Azure.KeyVault.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + public class LocalResolver : IKeyResolver + { + private Dictionary keys = new Dictionary(); + + public void Add(IKey key) + { + keys[key.Kid] = key; + } + + public async Task ResolveKeyAsync(string kid, CancellationToken token) + { + IKey result; + + keys.TryGetValue(kid, out result); + + return await Task.FromResult(result); + } + } +} diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/Program.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/Program.cs new file mode 100644 index 000000000..b07afb969 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/Program.cs @@ -0,0 +1,125 @@ +//---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- +namespace TableGettingStartedUsingResolver +{ + using Microsoft.Azure.KeyVault; + using Microsoft.WindowsAzure; + using Microsoft.WindowsAzure.Storage; + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Table; + using System; + using System.IO; + using System.Security.Cryptography; + + /// + /// Demonstrates how to use encryption with the Azure Table service. + /// + public class Program + { + const string DemoTable = "demotable"; + + static void Main(string[] args) + { + Console.WriteLine("Table encryption sample"); + + // Retrieve storage account information from connection string + // How to create a storage connection string - http://msdn.microsoft.com/en-us/library/azure/ee758697.aspx + CloudStorageAccount storageAccount = CreateStorageAccountFromConnectionString(CloudConfigurationManager.GetSetting("StorageConnectionString")); + CloudTableClient client = storageAccount.CreateCloudTableClient(); + CloudTable table = client.GetTableReference(DemoTable + Guid.NewGuid().ToString("N")); + + try + { + table.Create(); + + // Create the IKey used for encryption. + RsaKey key = new RsaKey("private:key1"); + + DynamicTableEntity ent = new DynamicTableEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; + ent.Properties.Add("EncryptedProp1", new EntityProperty(string.Empty)); + ent.Properties.Add("EncryptedProp2", new EntityProperty("bar")); + ent.Properties.Add("NotEncryptedProp", new EntityProperty(1234)); + + // This is used to indicate whether a property should be encrypted or not given the partition key, row key, + // and the property name. + Func encryptionResolver = (pk, rk, propName) => + { + if (propName.StartsWith("EncryptedProp")) + { + return true; + } + + return false; + }; + + TableRequestOptions insertOptions = new TableRequestOptions() + { + EncryptionPolicy = new TableEncryptionPolicy(key, null), + + EncryptionResolver = encryptionResolver + }; + + // Insert Entity + Console.WriteLine("Inserting the encrypted entity."); + table.Execute(TableOperation.Insert(ent), insertOptions, null); + + // For retrieves, a resolver can be set up that will help pick the key based on the key id. + LocalResolver resolver = new LocalResolver(); + resolver.Add(key); + + TableRequestOptions retrieveOptions = new TableRequestOptions() + { + EncryptionPolicy = new TableEncryptionPolicy(null, resolver) + }; + + // Retrieve Entity + Console.WriteLine("Retrieving the encrypted entity."); + TableOperation operation = TableOperation.Retrieve(ent.PartitionKey, ent.RowKey); + TableResult result = table.Execute(operation, retrieveOptions, null); + + Console.WriteLine("Press enter key to exit"); + Console.ReadLine(); + } + finally + { + table.DeleteIfExists(); + } + } + + private static CloudStorageAccount CreateStorageAccountFromConnectionString(string storageConnectionString) + { + CloudStorageAccount storageAccount; + try + { + storageAccount = CloudStorageAccount.Parse(storageConnectionString); + } + catch (FormatException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + throw; + } + catch (ArgumentException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + throw; + } + + return storageAccount; + } + } +} \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/Properties/AssemblyInfo.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..ede1e7b02 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TableGettingStarted")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TableGettingStarted")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("abd0736d-ec22-437b-a2ad-a6235558513c")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/TableGettingStartedUsingResolver.csproj b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/TableGettingStartedUsingResolver.csproj new file mode 100644 index 000000000..74183bf48 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/TableGettingStartedUsingResolver.csproj @@ -0,0 +1,142 @@ + + + + + Debug + AnyCPU + {449D75A7-140A-495A-B35A-9B056095F571} + Exe + Properties + TableGettingStarted + TableGettingStarted + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Hyak.Common.1.0.2\lib\net45\Hyak.Common.dll + True + + + ..\packages\Microsoft.Azure.Common.2.0.4\lib\net45\Microsoft.Azure.Common.dll + True + + + ..\packages\Microsoft.Azure.Common.2.0.4\lib\net45\Microsoft.Azure.Common.NetFramework.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.0.9.1-preview\lib\net45\Microsoft.Azure.KeyVault.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.Core.0.9.1-preview\lib\net40\Microsoft.Azure.KeyVault.Core.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.Extensions.0.9.1-preview\lib\net45\Microsoft.Azure.KeyVault.Extensions.dll + True + + + ..\packages\Microsoft.Data.Edm.5.6.2\lib\net40\Microsoft.Data.Edm.dll + True + + + ..\packages\Microsoft.Data.OData.5.6.2\lib\net40\Microsoft.Data.OData.dll + True + + + ..\packages\Microsoft.Data.Services.Client.5.6.2\lib\net40\Microsoft.Data.Services.Client.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll + True + + + ..\packages\Microsoft.WindowsAzure.ConfigurationManager.1.8.0.0\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll + True + + + ..\packages\WindowsAzure.Storage.4.4.0-preview\lib\net40\Microsoft.WindowsAzure.Storage.dll + True + + + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + True + + + + + + + ..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Extensions.dll + True + + + ..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Primitives.dll + True + + + + ..\packages\System.Spatial.5.6.2\lib\net40\System.Spatial.dll + True + + + + + + + + + + + + + + + Designer + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/packages.config b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/packages.config new file mode 100644 index 000000000..2133f779f --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStarted/packages.config @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/App.config b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/App.config new file mode 100644 index 000000000..083ba01a6 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/App.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/EncryptedEntity.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/EncryptedEntity.cs new file mode 100644 index 000000000..83d8ca46b --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/EncryptedEntity.cs @@ -0,0 +1,52 @@ +//---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- +namespace TableGettingStartedUsingAttributes +{ + using Microsoft.WindowsAzure.Storage.Table; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + public class EncryptedEntity : TableEntity + { + public EncryptedEntity() + { + } + + public EncryptedEntity(string pk, string rk) + : base(pk, rk) + { + } + + public void Populate() + { + this.EncryptedProperty1 = string.Empty; + this.EncryptedProperty2 = "foo"; + this.NotEncryptedProperty = "b"; + this.NotEncryptedIntProperty = 1234; + } + + [EncryptProperty] + public string EncryptedProperty1 { get; set; } + + [EncryptProperty] + public string EncryptedProperty2 { get; set; } + + public string NotEncryptedProperty { get; set; } + + public int NotEncryptedIntProperty { get; set; } + } +} diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/LocalResolver.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/LocalResolver.cs new file mode 100644 index 000000000..a24c6e309 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/LocalResolver.cs @@ -0,0 +1,42 @@ +//---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- +namespace TableGettingStartedUsingAttributes +{ + using Microsoft.Azure.KeyVault.Core; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + public class LocalResolver : IKeyResolver + { + private Dictionary keys = new Dictionary(); + + public void Add(IKey key) + { + keys[key.Kid] = key; + } + + public async Task ResolveKeyAsync(string kid, CancellationToken token) + { + IKey result; + + keys.TryGetValue(kid, out result); + + return await Task.FromResult(result); + } + } +} diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/Program.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/Program.cs new file mode 100644 index 000000000..f2b1750b2 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/Program.cs @@ -0,0 +1,109 @@ +//---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- +namespace TableGettingStartedUsingAttributes +{ + using Microsoft.Azure.KeyVault; + using Microsoft.WindowsAzure; + using Microsoft.WindowsAzure.Storage; + using Microsoft.WindowsAzure.Storage.Core; + using Microsoft.WindowsAzure.Storage.Table; + using System; + using System.IO; + using System.Security.Cryptography; + + /// + /// Demonstrates how to use encryption with the Azure Table service. + /// + public class Program + { + const string DemoTable = "demotable"; + + static void Main(string[] args) + { + Console.WriteLine("Table encryption sample"); + + // Retrieve storage account information from connection string + // How to create a storage connection string - http://msdn.microsoft.com/en-us/library/azure/ee758697.aspx + CloudStorageAccount storageAccount = CreateStorageAccountFromConnectionString(CloudConfigurationManager.GetSetting("StorageConnectionString")); + CloudTableClient client = storageAccount.CreateCloudTableClient(); + CloudTable table = client.GetTableReference(DemoTable + Guid.NewGuid().ToString("N")); + + try + { + table.Create(); + + // Create the IKey used for encryption. + RsaKey key = new RsaKey("private:key1"); + + EncryptedEntity ent = new EncryptedEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; + ent.Populate(); + + TableRequestOptions insertOptions = new TableRequestOptions() + { + EncryptionPolicy = new TableEncryptionPolicy(key, null) + }; + + // Insert Entity + Console.WriteLine("Inserting the encrypted entity."); + table.Execute(TableOperation.Insert(ent), insertOptions, null); + + // For retrieves, a resolver can be set up that will help pick the key based on the key id. + LocalResolver resolver = new LocalResolver(); + resolver.Add(key); + + TableRequestOptions retrieveOptions = new TableRequestOptions() + { + EncryptionPolicy = new TableEncryptionPolicy(null, resolver) + }; + + // Retrieve Entity + Console.WriteLine("Retrieving the encrypted entity."); + TableOperation operation = TableOperation.Retrieve(ent.PartitionKey, ent.RowKey); + TableResult result = table.Execute(operation, retrieveOptions, null); + + Console.WriteLine("Press enter key to exit"); + Console.ReadLine(); + } + finally + { + table.DeleteIfExists(); + } + } + + private static CloudStorageAccount CreateStorageAccountFromConnectionString(string storageConnectionString) + { + CloudStorageAccount storageAccount; + try + { + storageAccount = CloudStorageAccount.Parse(storageConnectionString); + } + catch (FormatException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + throw; + } + catch (ArgumentException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + throw; + } + + return storageAccount; + } + } +} \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/Properties/AssemblyInfo.cs b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..109886864 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TableGettingStartedUsingAttributes")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TableGettingStartedUsingAttributes")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bbaa3e35-34d7-40f9-90db-7f3484022527")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/TableGettingStartedUsingAttributes.csproj b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/TableGettingStartedUsingAttributes.csproj new file mode 100644 index 000000000..fb8fd4070 --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/TableGettingStartedUsingAttributes.csproj @@ -0,0 +1,141 @@ + + + + + Debug + AnyCPU + {804DF8D6-CCCC-4408-8B2E-2FF09CC3B266} + Exe + Properties + TableGettingStartedUsingAttributes + TableGettingStartedUsingAttributes + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Hyak.Common.1.0.2\lib\net45\Hyak.Common.dll + True + + + ..\packages\Microsoft.Azure.Common.2.0.4\lib\net45\Microsoft.Azure.Common.dll + True + + + ..\packages\Microsoft.Azure.Common.2.0.4\lib\net45\Microsoft.Azure.Common.NetFramework.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.0.9.1-preview\lib\net45\Microsoft.Azure.KeyVault.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.Core.0.9.1-preview\lib\net40\Microsoft.Azure.KeyVault.Core.dll + True + + + ..\packages\Microsoft.Azure.KeyVault.Extensions.0.9.1-preview\lib\net45\Microsoft.Azure.KeyVault.Extensions.dll + True + + + ..\packages\Microsoft.Data.Edm.5.6.2\lib\net40\Microsoft.Data.Edm.dll + True + + + ..\packages\Microsoft.Data.OData.5.6.2\lib\net40\Microsoft.Data.OData.dll + True + + + ..\packages\Microsoft.Data.Services.Client.5.6.2\lib\net40\Microsoft.Data.Services.Client.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll + True + + + ..\packages\Microsoft.WindowsAzure.ConfigurationManager.1.8.0.0\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll + True + + + ..\packages\WindowsAzure.Storage.4.4.0-preview\lib\net40\Microsoft.WindowsAzure.Storage.dll + True + + + ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + True + + + + + + + ..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Extensions.dll + True + + + ..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Primitives.dll + True + + + + ..\packages\System.Spatial.5.6.2\lib\net40\System.Spatial.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/packages.config b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/packages.config new file mode 100644 index 000000000..2133f779f --- /dev/null +++ b/Samples/GettingStarted/DotNet/EncryptionSamples/TableGettingStartedUsingAttributes/packages.config @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/App.config b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/App.config new file mode 100644 index 000000000..810d1d310 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/App.config @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/DataBlobStorage.csproj b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/DataBlobStorage.csproj new file mode 100644 index 000000000..ddad53863 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/DataBlobStorage.csproj @@ -0,0 +1,98 @@ + + + + + Debug + AnyCPU + {85C81265-CA86-4C3D-93B9-7AC54FCD20AF} + Exe + Properties + DataBlobStorage + DataBlobStorage + v4.5 + 512 + ..\..\ + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\..\packages\Microsoft.Data.Edm.5.6.2\lib\net40\Microsoft.Data.Edm.dll + + + False + ..\..\packages\Microsoft.Data.OData.5.6.2\lib\net40\Microsoft.Data.OData.dll + + + False + ..\..\packages\Microsoft.Data.Services.Client.5.6.2\lib\net40\Microsoft.Data.Services.Client.dll + + + False + ..\..\packages\Microsoft.WindowsAzure.ConfigurationManager.1.8.0.0\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll + + + False + ..\..\packages\WindowsAzure.Storage.4.3.0\lib\net40\Microsoft.WindowsAzure.Storage.dll + + + ..\..\packages\Newtonsoft.Json.5.0.8\lib\net45\Newtonsoft.Json.dll + + + + + + False + ..\..\packages\System.Spatial.5.6.2\lib\net40\System.Spatial.dll + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/HelloWorld.png b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/HelloWorld.png new file mode 100644 index 000000000..e098a8afc Binary files /dev/null and b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/HelloWorld.png differ diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/Program.cs b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/Program.cs new file mode 100644 index 000000000..7d3faff5b --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/Program.cs @@ -0,0 +1,239 @@ +//---------------------------------------------------------------------------------- +// Microsoft Developer & Platform Evangelism +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- + +namespace DataBlobStorageSample +{ + using Microsoft.WindowsAzure; + using Microsoft.WindowsAzure.Storage; + using Microsoft.WindowsAzure.Storage.Blob; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + + /// + /// Azure Storage Blob Sample - Demonstrate how to use the Blob Storage service. + /// Blob storage stores unstructured data such as text, binary data, documents or media files. + /// Blobs can be accessed from anywhere in the world via HTTP or HTTPS. + /// + /// Note: This sample uses the .NET 4.5 asynchronous programming model to demonstrate how to call the Storage Service using the + /// storage client libraries asynchronous API's. When used in real applications this approach enables you to improve the + /// responsiveness of your application. Calls to the storage service are prefixed by the await keyword. + /// + /// Documentation References: + /// - What is a Storage Account - http://azure.microsoft.com/en-us/documentation/articles/storage-whatis-account/ + /// - Getting Started with Blobs - http://azure.microsoft.com/en-us/documentation/articles/storage-dotnet-how-to-use-blobs/ + /// - Blob Service Concepts - http://msdn.microsoft.com/en-us/library/dd179376.aspx + /// - Blob Service REST API - http://msdn.microsoft.com/en-us/library/dd135733.aspx + /// - Blob Service C# API - http://go.microsoft.com/fwlink/?LinkID=398944 + /// - Delegating Access with Shared Access Signatures - http://azure.microsoft.com/en-us/documentation/articles/storage-dotnet-shared-access-signature-part-1/ + /// - Storage Emulator - http://msdn.microsoft.com/en-us/library/azure/hh403989.aspx + /// - Asynchronous Programming with Async and Await - http://msdn.microsoft.com/en-us/library/hh191443.aspx + /// + public class Program + { + // ************************************************************************************************************************* + // Instructions: This sample can be run using either the Azure Storage Emulator that installs as part of this SDK - or by + // updating the App.Config file with your AccountName and Key. + // + // To run the sample using the Storage Emulator (default option) + // 1. Start the Azure Storage Emulator (once only) by pressing the Start button or the Windows key and searching for it + // by typing "Azure Storage Emulator". Select it from the list of applications to start it. + // 2. Set breakpoints and run the project using F10. + // + // To run the sample using the Storage Service + // 1. Open the app.config file and comment out the connection string for the emulator (UseDevelopmentStorage=True) and + // uncomment the connection string for the storage service (AccountName=[]...) + // 2. Create a Storage Account through the Azure Portal and provide your [AccountName] and [AccountKey] in + // the App.Config file. See http://go.microsoft.com/fwlink/?LinkId=325277 for more information + // 3. Set breakpoints and run the project using F10. + // + // ************************************************************************************************************************* + static void Main(string[] args) + { + Console.WriteLine("Azure Storage Blob Sample\n "); + + // Block blob basics + Console.WriteLine("Block Blob Sample"); + BasicStorageBlockBlobOperationsAsync().Wait(); + + // Page blob basics + Console.WriteLine("\nPage Blob Sample"); + BasicStoragePageBlobOperationsAsync().Wait(); + + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + } + + /// + /// Basic operations to work with block blobs + /// + /// Task + private static async Task BasicStorageBlockBlobOperationsAsync() + { + const string ImageToUpload = "HelloWorld.png"; + + // Retrieve storage account information from connection string + // How to create a storage connection string - http://msdn.microsoft.com/en-us/library/azure/ee758697.aspx + CloudStorageAccount storageAccount = CreateStorageAccountFromConnectionString(CloudConfigurationManager.GetSetting("StorageConnectionString")); + + // Create a blob client for interacting with the blob service. + CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient(); + + // Create a container for organizing blobs within the storage account. + Console.WriteLine("1. Creating Container"); + CloudBlobContainer container = blobClient.GetContainerReference("democontainerblockblob"); + try + { + await container.CreateIfNotExistsAsync(); + } + catch (StorageException) + { + Console.WriteLine("If you are running with the default configuration please make sure you have started the storage emulator. Press the Windows key and type Azure Storage to select and run it from the list of applications - then restart the sample."); + Console.ReadLine(); + throw; + } + + // To view the uploaded blob in a browser, you have two options. The first option is to use a Shared Access Signature (SAS) token to delegate + // access to the resource. See the documentation links at the top for more information on SAS. The second approach is to set permissions + // to allow public access to blobs in this container. Uncomment the line below to use this approach. Then you can view the image + // using: https://[InsertYourStorageAccountNameHere].blob.core.windows.net/democontainer/HelloWorld.png + // await container.SetPermissionsAsync(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Blob }); + + // Upload a BlockBlob to the newly created container + Console.WriteLine("2. Uploading BlockBlob"); + CloudBlockBlob blockBlob = container.GetBlockBlobReference(ImageToUpload); + await blockBlob.UploadFromFileAsync(ImageToUpload, FileMode.Open); + + // List all the blobs in the container + Console.WriteLine("3. List Blobs in Container"); + foreach (IListBlobItem blob in container.ListBlobs()) + { + // Blob type will be CloudBlockBlob, CloudPageBlob or CloudBlobDirectory + // Use blob.GetType() and cast to appropriate type to gain access to properties specific to each type + Console.WriteLine("- {0} (type: {1})", blob.Uri, blob.GetType()); + } + + // Download a blob to your file system + Console.WriteLine("4. Download Blob from {0}", blockBlob.Uri.AbsoluteUri); + await blockBlob.DownloadToFileAsync(string.Format("./CopyOf{0}", ImageToUpload), FileMode.Create); + + // Clean up after the demo + Console.WriteLine("5. Delete block Blob"); + await blockBlob.DeleteAsync(); + + // When you delete a container it could take several seconds before you can recreate a container with the same + // name - hence to enable you to run the demo in quick succession the container is not deleted. If you want + // to delete the container uncomment the line of code below. + //Console.WriteLine("6. Delete Container"); + //await container.DeleteAsync(); + } + + /// + /// Basic operations to work with page blobs + /// + /// Task + private static async Task BasicStoragePageBlobOperationsAsync() + { + const string PageBlobName = "samplepageblob"; + + // Retrieve storage account information from connection string + // How to create a storage connection string - http://msdn.microsoft.com/en-us/library/azure/ee758697.aspx + CloudStorageAccount storageAccount = CreateStorageAccountFromConnectionString(CloudConfigurationManager.GetSetting("StorageConnectionString")); + + // Create a blob client for interacting with the blob service. + CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient(); + + // Create a container for organizing blobs within the storage account. + Console.WriteLine("1. Creating Container"); + CloudBlobContainer container = blobClient.GetContainerReference("democontainerpageblob"); + await container.CreateIfNotExistsAsync(); + + // Create a page blob in the newly created container. + Console.WriteLine("2. Creating Page Blob"); + CloudPageBlob pageBlob = container.GetPageBlobReference(PageBlobName); + await pageBlob.CreateAsync(512 * 2 /*size*/); // size needs to be multiple of 512 bytes + + // Write to a page blob + Console.WriteLine("2. Write to a Page Blob"); + byte[] samplePagedata = new byte[512]; + Random random = new Random(); + random.NextBytes(samplePagedata); + await pageBlob.UploadFromByteArrayAsync(samplePagedata, 0, samplePagedata.Length); + + // List all blobs in this container. Because a container can contain a large number of blobs the results + // are returned in segments (pages) with a maximum of 5000 blobs per segment. You can define a smaller size + // using the maxResults parameter on ListBlobsSegmentedAsync. + Console.WriteLine("3. List Blobs in Container"); + BlobContinuationToken token = null; + do + { + BlobResultSegment resultSegment = await container.ListBlobsSegmentedAsync(token); + token = resultSegment.ContinuationToken; + foreach (IListBlobItem blob in resultSegment.Results) + { + // Blob type will be CloudBlockBlob, CloudPageBlob or CloudBlobDirectory + Console.WriteLine("{0} (type: {1}", blob.Uri, blob.GetType()); + } + } while (token != null); + + // Read from a page blob + //Console.WriteLine("4. Read from a Page Blob"); + int bytesRead = await pageBlob.DownloadRangeToByteArrayAsync(samplePagedata, 0, 0, samplePagedata.Count()); + + // Clean up after the demo + Console.WriteLine("5. Delete page Blob"); + await pageBlob.DeleteAsync(); + + // When you delete a container it could take several seconds before you can recreate a container with the same + // name - hence to enable you to run the demo in quick succession the container is not deleted. If you want + // to delete the container uncomment the line of code below. + //Console.WriteLine("6. Delete Container"); + //await container.DeleteAsync(); + } + + /// + /// Validates the connection string information in app.config and throws an exception if it looks like + /// the user hasn't updated this to valid values. + /// + /// The storage connection string + /// CloudStorageAccount object + private static CloudStorageAccount CreateStorageAccountFromConnectionString(string storageConnectionString) + { + CloudStorageAccount storageAccount; + try + { + storageAccount = CloudStorageAccount.Parse(storageConnectionString); + } + catch (FormatException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.ReadLine(); + throw; + } + catch (ArgumentException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.ReadLine(); + throw; + } + + return storageAccount; + } + + } +} diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/Properties/AssemblyInfo.cs b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..8ccf9b25d --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DataBlobStorage")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DataBlobStorage")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("02db5912-44fc-4b98-a1f9-854d6e6441fe")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/_Definitions/_project.vstemplate.xml b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/_Definitions/_project.vstemplate.xml new file mode 100644 index 000000000..fbeb474f5 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/_Definitions/_project.vstemplate.xml @@ -0,0 +1,27 @@ + + + Azure Blob Storage + Demonstrates how to use Azure Blob Storage for your files + DataBlobStorage + + CSharp + + 1000 + true + true + Enabled + true + sw-file-icon.png + + + 1 + + + + + + + ProjectWizard, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=caa87a55fd76d99d + ProjectWizard.Wizard + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/_preprocess.xml b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/_preprocess.xml new file mode 100644 index 000000000..3147b77b9 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/_preprocess.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/packages.config b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/packages.config new file mode 100644 index 000000000..5cdca6448 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/sw-file-icon.png b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/sw-file-icon.png new file mode 100644 index 000000000..ace84333e Binary files /dev/null and b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataBlobStorage/sw-file-icon.png differ diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/App.config b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/App.config new file mode 100644 index 000000000..7c55fb964 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/App.config @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/DataFileStorage.csproj b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/DataFileStorage.csproj new file mode 100644 index 000000000..23c688b29 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/DataFileStorage.csproj @@ -0,0 +1,97 @@ + + + + + Debug + AnyCPU + {6498E995-C760-4920-8FF8-4A3EE3842D20} + Exe + Properties + DataFileStorage + DataFileStorage + v4.5 + 512 + ..\..\ + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\packages\Microsoft.Data.Edm.5.6.2\lib\net40\Microsoft.Data.Edm.dll + + + False + ..\packages\Microsoft.Data.OData.5.6.2\lib\net40\Microsoft.Data.OData.dll + + + False + ..\packages\Microsoft.Data.Services.Client.5.6.2\lib\net40\Microsoft.Data.Services.Client.dll + + + False + ..\packages\Microsoft.WindowsAzure.ConfigurationManager.1.8.0.0\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll + + + False + ..\packages\WindowsAzure.Storage.4.3.0\lib\net40\Microsoft.WindowsAzure.Storage.dll + + + ..\packages\Newtonsoft.Json.5.0.8\lib\net45\Newtonsoft.Json.dll + + + + + + False + ..\packages\System.Spatial.5.6.2\lib\net40\System.Spatial.dll + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/HelloWorld.png b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/HelloWorld.png new file mode 100644 index 000000000..e098a8afc Binary files /dev/null and b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/HelloWorld.png differ diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/Program.cs b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/Program.cs new file mode 100644 index 000000000..8ca9ba931 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/Program.cs @@ -0,0 +1,175 @@ +//---------------------------------------------------------------------------------- +// Microsoft Developer & Platform Evangelism +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- + +namespace DataFileStorageSample +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Threading.Tasks; + using Microsoft.WindowsAzure; + using Microsoft.WindowsAzure.Storage; + using Microsoft.WindowsAzure.Storage.File; + + /// + /// Azure Storage File Sample - Demonstrate how to use the File Storage service. + /// + /// Note: This sample uses the .NET 4.5 asynchronous programming model to demonstrate how to call the Storage Service using the + /// storage client libraries asynchronous API's. When used in real applications this approach enables you to improve the + /// responsiveness of your application. Calls to the storage service are prefixed by the await keyword. + /// + /// Documentation References: + /// - What is a Storage Account - http://azure.microsoft.com/en-us/documentation/articles/storage-whatis-account/ + /// - Getting Started with Files - http://blogs.msdn.com/b/windowsazurestorage/archive/2014/05/12/introducing-microsoft-azure-file-service.aspx + /// - How to use Azure File Storage - http://azure.microsoft.com/en-us/documentation/articles/storage-dotnet-how-to-use-files/ + /// - File Service Concepts - http://msdn.microsoft.com/en-us/library/dn166972.aspx + /// - File Service REST API - http://msdn.microsoft.com/en-us/library/dn167006.aspx + /// - File Service C# API - http://msdn.microsoft.com/en-us/library/microsoft.windowsazure.storage.file.aspx + /// - Asynchronous Programming with Async and Await - http://msdn.microsoft.com/en-us/library/hh191443.aspx + /// + public class Program + { + // ************************************************************************************************************************* + // Instructions: This sample can be run against Microsoft Azure Storage Service by updating the App.Config with your AccountName and AccountKey. + // + // To run the sample using the Storage Service + // 1. Create a Storage Account through the Azure Portal and provide your [AccountName] and [AccountKey] in + // the App.Config file. See http://go.microsoft.com/fwlink/?LinkId=325277 for more information + // 2. Set breakpoints and run the project using F10. + // + // ************************************************************************************************************************* + static void Main(string[] args) + { + Console.WriteLine("Azure Storage File Sample\n "); + + BasicAzureFileOperationsAsync().Wait(); + + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + } + + /// + /// Basic operations to work with Azure Files + /// + /// Task + private static async Task BasicAzureFileOperationsAsync() + { + const string DemoShare = "demofileshare"; + const string DemoDirectory = "demofiledirectory"; + const string ImageToUpload = "HelloWorld.png"; + + // Retrieve storage account information from connection string + // How to create a storage connection string - http://msdn.microsoft.com/en-us/library/azure/ee758697.aspx + CloudStorageAccount storageAccount = CreateStorageAccountFromConnectionString(CloudConfigurationManager.GetSetting("StorageConnectionString")); + + // Create a file client for interacting with the file service. + CloudFileClient fileClient = storageAccount.CreateCloudFileClient(); + + // Create a share for organizing files and directories within the storage account. + Console.WriteLine("1. Creating file share"); + CloudFileShare share = fileClient.GetShareReference(DemoShare); + + try + { + await share.CreateIfNotExistsAsync(); + } + catch (StorageException) + { + Console.WriteLine("Please make sure your storage account has storage file endpoint enabled and specified correctly in the app.config - then restart the sample."); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + throw; + } + + // Get a reference to the root directory of the share. + CloudFileDirectory root = share.GetRootDirectoryReference(); + + // Create a directory under the root directory + Console.WriteLine("2. Creating a directory under the root directory"); + CloudFileDirectory dir = root.GetDirectoryReference(DemoDirectory); + await dir.CreateIfNotExistsAsync(); + + // Uploading a local file to the directory created above + Console.WriteLine("3. Uploading a file to directory"); + CloudFile file = dir.GetFileReference(ImageToUpload); + await file.UploadFromFileAsync(ImageToUpload, FileMode.Open); + + // List all files/directories under the root directory + Console.WriteLine("4. List Files/Directories in root directory"); + List results = new List(); + FileContinuationToken token = null; + do + { + FileResultSegment resultSegment = await share.GetRootDirectoryReference().ListFilesAndDirectoriesSegmentedAsync(token); + results.AddRange(resultSegment.Results); + token = resultSegment.ContinuationToken; + } + while (token != null); + + // Print all files/directories listed above + foreach (IListFileItem listItem in results) + { + // listItem type will be CloudFile or CloudFileDirectory + Console.WriteLine("- {0} (type: {1})", listItem.Uri, listItem.GetType()); + } + + // Download the uploaded file to your file system + Console.WriteLine("5. Download file from {0}", file.Uri.AbsoluteUri); + await file.DownloadToFileAsync(string.Format("./CopyOf{0}", ImageToUpload), FileMode.Create); + + // Clean up after the demo + Console.WriteLine("6. Delete file"); + await file.DeleteAsync(); + + // When you delete a share it could take several seconds before you can recreate a share with the same + // name - hence to enable you to run the demo in quick succession the share is not deleted. If you want + // to delete the share uncomment the line of code below. + // Console.WriteLine("7. Delete Share"); + // await share.DeleteAsync(); + } + + /// + /// Validates the connection string information in app.config and throws an exception if it looks like + /// the user hasn't updated this to valid values. + /// + /// The storage connection string + /// CloudStorageAccount object + private static CloudStorageAccount CreateStorageAccountFromConnectionString(string storageConnectionString) + { + CloudStorageAccount storageAccount; + try + { + storageAccount = CloudStorageAccount.Parse(storageConnectionString); + } + catch (FormatException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + throw; + } + catch (ArgumentException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + throw; + } + + return storageAccount; + } + } +} diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/Properties/AssemblyInfo.cs b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..252c90ab1 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DataFileStorage")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DataFileStorage")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("02db5912-44fc-4b98-a1f9-854d6e6441fe")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/_Definitions/_project.vstemplate.xml b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/_Definitions/_project.vstemplate.xml new file mode 100644 index 000000000..45b257d9a --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/_Definitions/_project.vstemplate.xml @@ -0,0 +1,27 @@ + + + Azure File Storage + Demonstrates how to use Azure File Storage service using REST protocol + DataFileStorage + + CSharp + + 1000 + true + true + Enabled + true + sw-file-icon.png + + + 1 + + + + + + + ProjectWizard, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=caa87a55fd76d99d + ProjectWizard.Wizard + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/_preprocess.xml b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/_preprocess.xml new file mode 100644 index 000000000..ef1db6731 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/_preprocess.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/packages.config b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/packages.config new file mode 100644 index 000000000..5cdca6448 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/sw-file-icon.png b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/sw-file-icon.png new file mode 100644 index 000000000..ace84333e Binary files /dev/null and b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataFileStorage/sw-file-icon.png differ diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/App.config b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/App.config new file mode 100644 index 000000000..60a6e5884 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/App.config @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/DataStorageQueue.csproj b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/DataStorageQueue.csproj new file mode 100644 index 000000000..5edfae84e --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/DataStorageQueue.csproj @@ -0,0 +1,92 @@ + + + + + Debug + AnyCPU + {FB99BAF4-BE6E-43F2-B903-50F0049AC496} + Exe + Properties + DataStorageQueue + DataStorageQueue + v4.5 + 512 + ..\..\ + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + $(SolutionDir)\packages\Microsoft.Data.Edm.5.6.0\lib\net40\Microsoft.Data.Edm.dll + + + $(SolutionDir)\packages\Microsoft.Data.OData.5.6.0\lib\net40\Microsoft.Data.OData.dll + + + $(SolutionDir)\packages\Microsoft.Data.Services.Client.5.6.0\lib\net40\Microsoft.Data.Services.Client.dll + + + False + $(SolutionDir)\packages\Microsoft.WindowsAzure.ConfigurationManager.1.8.0.0\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll + + + False + $(SolutionDir)\packages\WindowsAzure.Storage.3.1.0.1\lib\net40\Microsoft.WindowsAzure.Storage.dll + + + $(SolutionDir)\packages\Newtonsoft.Json.5.0.6\lib\net45\Newtonsoft.Json.dll + + + + + + $(SolutionDir)\packages\System.Spatial.5.6.0\lib\net40\System.Spatial.dll + + + + + + + + + + + + + + + + + + Designer + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/Program.cs b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/Program.cs new file mode 100644 index 000000000..8a2863568 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/Program.cs @@ -0,0 +1,240 @@ +//---------------------------------------------------------------------------------- +// Microsoft Developer & Platform Evangelism +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- +namespace DataStorageQueueSample +{ + using Microsoft.WindowsAzure; + using Microsoft.WindowsAzure.Storage; + using Microsoft.WindowsAzure.Storage.Queue; + using System; + using System.Threading.Tasks; + + /// + /// Azure Queue Service Sample - The Queue Service provides reliable messaging for workflow processing and for communication + /// between loosely coupled components of cloud services. This sample demonstrates how to perform common tasks including + /// inserting, peeking, getting and deleting queue messages, as well as creating and deleting queues. + /// + /// Note: This sample uses the .NET 4.5 asynchronous programming model to demonstrate how to call the Storage Service using the + /// storage client libraries asynchronous API's. When used in real applications this approach enables you to improve the + /// responsiveness of your application. Calls to the storage service are prefixed by the await keyword. + /// + /// Documentation References: + /// - What is a Storage Account - http://azure.microsoft.com/en-us/documentation/articles/storage-whatis-account/ + /// - Getting Started with Queues - http://azure.microsoft.com/en-us/documentation/articles/storage-dotnet-how-to-use-queues/ + /// - Queue Service Concepts - http://msdn.microsoft.com/en-us/library/dd179353.aspx + /// - Queue Service REST API - http://msdn.microsoft.com/en-us/library/dd179363.aspx + /// - Queue Service C# API - http://go.microsoft.com/fwlink/?LinkID=398944 + /// - Storage Emulator - http://msdn.microsoft.com/en-us/library/azure/hh403989.aspx + /// - Asynchronous Programming with Async and Await - http://msdn.microsoft.com/en-us/library/hh191443.aspx + /// + public class Program + { + // ************************************************************************************************************************* + // Instructions: This sample can be run using either the Azure Storage Emulator that installs as part of this SDK - or by + // updating the App.Config file with your AccountName and Key. + // + // To run the sample using the Storage Emulator (default option) + // 1. Start the Azure Storage Emulator (once only) by pressing the Start button or the Windows key and searching for it + // by typing "Azure Storage Emulator". Select it from the list of applications to start it. + // 2. Set breakpoints and run the project using F10. + // + // To run the sample using the Storage Service + // 1. Open the app.config file and comment out the connection string for the emulator (UseDevelopmentStorage=True) and + // uncomment the connection string for the storage service (AccountName=[]...) + // 2. Create a Storage Account through the Azure Portal and provide your [AccountName] and [AccountKey] in + // the App.Config file. See http://go.microsoft.com/fwlink/?LinkId=325277 for more information + // 3. Set breakpoints and run the project using F10. + // + // ************************************************************************************************************************* + public static void Main(string[] args) + { + Console.WriteLine("Azure Storage Queue Sample\n"); + + // Create or reference an existing queue + CloudQueue queue = CreateQueueAsync().Result; + + // Demonstrate basic queue functionality + BasicQueueOperationsAsync(queue).Wait(); + + // Demonstrate how to update an enqueued message + UpdateEnqueuedMessageAsync(queue).Wait(); + + // Demonstrate advanced functionality such as processing of batches of messages + ProcessBatchOfMessagesAsync(queue).Wait(); + + // When you delete a queue it could take several seconds before you can recreate a queue with the same + // name - hence to enable you to run the demo in quick succession the queue is not deleted. If you want + // to delete the queue uncomment the line of code below. + //DeleteQueueAsync(queue).Wait(); + + Console.WriteLine("Press any key to exit"); + Console.Read(); + } + + + /// + /// Create a queue for the sample application to process messages in. + /// + /// A CloudQueue object + private static async Task CreateQueueAsync() + { + // Retrieve storage account information from connection string. + CloudStorageAccount storageAccount = CreateStorageAccountFromConnectionString(CloudConfigurationManager.GetSetting("StorageConnectionString")); + + // Create a queue client for interacting with the queue service + CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient(); + + Console.WriteLine("1. Create a queue for the demo"); + CloudQueue queue = queueClient.GetQueueReference("samplequeue"); + try + { + await queue.CreateIfNotExistsAsync(); + } + catch (StorageException ex) + { + Console.WriteLine("If you are running with the default configuration please make sure you have started the storage emulator. Press the Windows key and type Azure Storage to select and run it from the list of applications - then restart the sample."); + Console.ReadLine(); + throw; + } + + return queue; + } + + /// + /// Demonstrate basic queue operations such as adding a message to a queue, peeking at the front of the queue and dequeing a message. + /// + /// The sample queue + private static async Task BasicQueueOperationsAsync(CloudQueue queue) + { + // Insert a message into the queue using the AddMessage method. + Console.WriteLine("2. Insert a single message into a queue"); + await queue.AddMessageAsync(new CloudQueueMessage("Hello World!")); + + // Peek at the message in the front of a queue without removing it from the queue using PeekMessage (PeekMessages lets you peek >1 message) + Console.WriteLine("3. Peek at the next message"); + CloudQueueMessage peekedMessage = await queue.PeekMessageAsync(); + if (peekedMessage != null) + { + Console.WriteLine("The peeked message is: {0}", peekedMessage.AsString); + } + + // You de-queue a message in two steps. Call GetMessage at which point the message becomes invisible to any other code reading messages + // from this queue for a default period of 30 seconds. To finish removing the message from the queue, you call DeleteMessage. + // This two-step process ensures that if your code fails to process a message due to hardware or software failure, another instance + // of your code can get the same message and try again. + Console.WriteLine("4. De-queue the next message"); + CloudQueueMessage message = await queue.GetMessageAsync(); + if (message != null) + { + Console.WriteLine("Processing & deleting message with content: {0}", message.AsString); + await queue.DeleteMessageAsync(message); + } + } + + /// + /// Update an enqueued message and its visibility time. For workflow scenarios this could enable you to update + /// the status of a task as well as extend the visibility timeout in order to provide more time for a client + /// continue working on the message before another client can see the message. + /// + /// The sample queue + private static async Task UpdateEnqueuedMessageAsync(CloudQueue queue) + { + // Insert another test message into the queue + Console.WriteLine("5. Insert another test message "); + await queue.AddMessageAsync(new CloudQueueMessage("Hello World Again!")); + + Console.WriteLine("6. Change the contents of a queued message"); + CloudQueueMessage message = await queue.GetMessageAsync(); + message.SetMessageContent("Updated contents."); + await queue.UpdateMessageAsync( + message, + TimeSpan.Zero, // For the purpose of the sample make the update visible immediately + MessageUpdateFields.Content | + MessageUpdateFields.Visibility); + } + + /// + /// Demonstrate adding a number of messages, checking the message count and batch retrieval of messages. During retrieval we + /// also set the visibility timeout to 5 minutes. Visibility timeout is the amount of time message is not visible to other + /// clients after a GetMessageOperation assuming DeleteMessage is not called. + /// + /// The sample queue + private static async Task ProcessBatchOfMessagesAsync(CloudQueue queue) + { + // Enqueue 20 messages by which to demonstrate batch retrieval + Console.WriteLine("7. Enqueue 20 messages."); + for (int i = 0; i < 20; i++) + { + await queue.AddMessageAsync(new CloudQueueMessage(string.Format("{0} - {1}", i, "Hello World"))); + } + + // The FetchAttributes method asks the Queue service to retrieve the queue attributes, including an approximation of message count + Console.WriteLine("8. Get the queue length"); + queue.FetchAttributes(); + int? cachedMessageCount = queue.ApproximateMessageCount; + Console.WriteLine("Number of messages in queue: {0}", cachedMessageCount); + + // Dequeue a batch of 21 messages (up to 32) and set visibility timeout to 5 minutes. Note we are dequeuing 21 messages because the earlier + // UpdateEnqueuedMessage method left a message on the queue hence we are retrieving that as well. + Console.WriteLine("9. Dequeue 21 messages, allowing 5 minutes for the clients to process."); + foreach (CloudQueueMessage msg in await queue.GetMessagesAsync(21, TimeSpan.FromMinutes(5), null, null)) + { + Console.WriteLine("Processing & deleting message with content: {0}", msg.AsString); + + // Process all messages in less than 5 minutes, deleting each message after processing. + await queue.DeleteMessageAsync(msg); + } + } + + /// + /// Validate the connection string information in app.config and throws an exception if it looks like + /// the user hasn't updated this to valid values. + /// + /// The storage connection string + /// CloudStorageAccount object + private static CloudStorageAccount CreateStorageAccountFromConnectionString(string storageConnectionString) + { + CloudStorageAccount storageAccount; + try + { + storageAccount = CloudStorageAccount.Parse(storageConnectionString); + } + catch (FormatException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.ReadLine(); + throw; + } + catch (ArgumentException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.ReadLine(); + throw; + } + + return storageAccount; + } + + /// + /// Delete the queue that was created for this sample + /// + /// The sample queue to delete + private static async Task DeleteQueueAsync(CloudQueue queue) + { + Console.WriteLine("10. Delete the queue"); + await queue.DeleteAsync(); + } + } +} diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/Properties/AssemblyInfo.cs b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..986cc5e93 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DataStorageQueue")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DataStorageQueue")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3f5988f3-3ac6-4515-a658-dff69273c675")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/_Definitions/_project.vstemplate.xml b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/_Definitions/_project.vstemplate.xml new file mode 100644 index 000000000..ea8de2d54 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/_Definitions/_project.vstemplate.xml @@ -0,0 +1,27 @@ + + + Azure Storage Queues + Demonstrates include inserting, peeking, getting, and deleting queue messages, as well as creating and deleting queues using the Azure Storage Client for .NET. + DataStorageQueue + + CSharp + + 1000 + true + true + Enabled + true + sw-file-icon.png + + + 1 + + + + + + + ProjectWizard, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=caa87a55fd76d99d + ProjectWizard.Wizard + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/_preprocess.xml b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/_preprocess.xml new file mode 100644 index 000000000..87353ffa3 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/_preprocess.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/packages.config b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/packages.config new file mode 100644 index 000000000..5cdca6448 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/sw-file-icon.png b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/sw-file-icon.png new file mode 100644 index 000000000..5db3a286b Binary files /dev/null and b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataStorageQueue/sw-file-icon.png differ diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/App.config b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/App.config new file mode 100644 index 000000000..3b4eaeb11 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/App.config @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/DataTableStorage.csproj b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/DataTableStorage.csproj new file mode 100644 index 000000000..b8188284b --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/DataTableStorage.csproj @@ -0,0 +1,93 @@ + + + + + Debug + AnyCPU + {058D9126-C9C7-4CB2-B38A-5849F7BFC32D} + Exe + Properties + DataTableStorage + DataTableStorage + v4.5 + 512 + ..\..\ + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + $(SolutionDir)\packages\Microsoft.Data.Edm.5.6.0\lib\net40\Microsoft.Data.Edm.dll + + + $(SolutionDir)\packages\Microsoft.Data.OData.5.6.0\lib\net40\Microsoft.Data.OData.dll + + + $(SolutionDir)\packages\Microsoft.Data.Services.Client.5.6.0\lib\net40\Microsoft.Data.Services.Client.dll + + + False + $(SolutionDir)\packages\Microsoft.WindowsAzure.ConfigurationManager.1.8.0.0\lib\net35-full\Microsoft.WindowsAzure.Configuration.dll + + + False + $(SolutionDir)\packages\WindowsAzure.Storage.3.1.0.1\lib\net40\Microsoft.WindowsAzure.Storage.dll + + + $(SolutionDir)\packages\Newtonsoft.Json.5.0.6\lib\net45\Newtonsoft.Json.dll + + + + + + $(SolutionDir)\packages\System.Spatial.5.6.0\lib\net40\System.Spatial.dll + + + + + + + + + + + + + + + + + + + Designer + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/Model/CustomerEntity.cs b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/Model/CustomerEntity.cs new file mode 100644 index 000000000..5d77a1420 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/Model/CustomerEntity.cs @@ -0,0 +1,41 @@ +//---------------------------------------------------------------------------------- +// Microsoft Developer & Platform Evangelism +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- +namespace DataTableStorageSample.Model +{ + using Microsoft.WindowsAzure.Storage.Table; + + /// + /// Define a Customer entity for demonstrating the Table Service. For the purposes of the sample we use the + /// customer's first name as the row key and last name as the partition key. In reality this would not be a good + /// PK and RK combination as it would likely not be gauranteed to be unique which is one of the requirements for an entity. + /// + public class CustomerEntity : TableEntity + { + // Your entity type must expose a parameter-less constructor + public CustomerEntity() { } + + // Define the PK and RK + public CustomerEntity(string lastName, string firstName) + { + this.PartitionKey = lastName; + this.RowKey = firstName; + } + + //For any property that should be stored in the table service, the property must be a public property of a supported type that exposes both get and set. + public string Email { get; set; } + public string PhoneNumber { get; set; } + } +} diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/Program.cs b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/Program.cs new file mode 100644 index 000000000..b4e6b5133 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/Program.cs @@ -0,0 +1,365 @@ +//---------------------------------------------------------------------------------- +// Microsoft Developer & Platform Evangelism +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +//---------------------------------------------------------------------------------- +// The example companies, organizations, products, domain names, +// e-mail addresses, logos, people, places, and events depicted +// herein are fictitious. No association with any real company, +// organization, product, domain name, email address, logo, person, +// places, or events is intended or should be inferred. +//---------------------------------------------------------------------------------- + +namespace DataTableStorageSample +{ + using DataTableStorageSample.Model; + using Microsoft.WindowsAzure; + using Microsoft.WindowsAzure.Storage; + using Microsoft.WindowsAzure.Storage.Table; + using System; + using System.Collections.Generic; + using System.Net; + using System.Threading.Tasks; + + /// + /// Azure Table Service Sample - Demonstrate how to perform common tasks using the Microsoft Azure Table storage + /// including creating a table, CRUD operations, batch operations and different querying techniques. + /// + /// Note: This sample uses the .NET 4.5 asynchronous programming model to demonstrate how to call the Storage Service using the + /// storage client libraries asynchronous API's. When used in real applications this approach enables you to improve the + /// responsiveness of your application. Calls to the storage service are prefixed by the await keyword. + /// + /// Documentation References: + /// - What is a Storage Account - http://azure.microsoft.com/en-us/documentation/articles/storage-whatis-account/ + /// - Getting Started with Tables - http://azure.microsoft.com/en-us/documentation/articles/storage-dotnet-how-to-use-tables/ + /// - Table Service Concepts - http://msdn.microsoft.com/en-us/library/dd179463.aspx + /// - Table Service REST API - http://msdn.microsoft.com/en-us/library/dd179423.aspx + /// - Table Service C# API - http://go.microsoft.com/fwlink/?LinkID=398944 + /// - Storage Emulator - http://msdn.microsoft.com/en-us/library/azure/hh403989.aspx + /// - Asynchronous Programming with Async and Await - http://msdn.microsoft.com/en-us/library/hh191443.aspx + /// + + public class Program + { + // ************************************************************************************************************************* + // Instructions: This sample can be run using either the Azure Storage Emulator that installs as part of this SDK - or by + // updating the App.Config file with your AccountName and Key. + // + // To run the sample using the Storage Emulator (default option) + // 1. Start the Azure Storage Emulator (once only) by pressing the Start button or the Windows key and searching for it + // by typing "Azure Storage Emulator". Select it from the list of applications to start it. + // 2. Set breakpoints and run the project using F10. + // + // To run the sample using the Storage Service + // 1. Open the app.config file and comment out the connection string for the emulator (UseDevelopmentStorage=True) and + // uncomment the connection string for the storage service (AccountName=[]...) + // 2. Create a Storage Account through the Azure Portal and provide your [AccountName] and [AccountKey] in + // the App.Config file. See http://go.microsoft.com/fwlink/?LinkId=325277 for more information + // 3. Set breakpoints and run the project using F10. + // + // ************************************************************************************************************************* + internal const string TableName = "customer"; + + public static void Main(string[] args) + { + Console.WriteLine("Azure Storage Table Sample\n"); + + // Create or reference an existing table + CloudTable table = CreateTableAsync().Result; + + // Demonstrate basic CRUD functionality + BasicTableOperationsAsync(table).Wait(); + + // Demonstrate advanced functionality such as batch operations and segmented multi-entity queries + AdvancedTableOperationsAsync(table).Wait(); + + // When you delete a table it could take several seconds before you can recreate a table with the same + // name - hence to enable you to run the demo in quick succession the table is not deleted. If you want + // to delete the table uncomment the line of code below. + //DeleteTableAsync(table).Wait(); + + Console.WriteLine("Press any key to exit"); + Console.Read(); + } + + /// + /// Create a table for the sample application to process messages in. + /// + /// A CloudTable object + private static async Task CreateTableAsync() + { + // Retrieve storage account information from connection string. + CloudStorageAccount storageAccount = CreateStorageAccountFromConnectionString(CloudConfigurationManager.GetSetting("StorageConnectionString")); + + // Create a table client for interacting with the table service + CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); + + Console.WriteLine("1. Create a Table for the demo"); + + // Create a table client for interacting with the table service + CloudTable table = tableClient.GetTableReference(TableName); + try + { + if (await table.CreateIfNotExistsAsync()) + { + Console.WriteLine("Created Table named: {0}", TableName); + } + else + { + Console.WriteLine("Table {0} already exists", TableName); + } + } + catch (StorageException) + { + Console.WriteLine("If you are running with the default configuration please make sure you have started the storage emulator. Press the Windows key and type Azure Storage to select and run it from the list of applications - then restart the sample."); + Console.ReadLine(); + throw; + } + + return table; + } + + /// + /// Demonstrate basic Table CRUD operations. + /// + /// The sample table + private static async Task BasicTableOperationsAsync(CloudTable table) + { + // Create an instance of a customer entity. See the Model\CustomerEntity.cs for a description of the entity. + CustomerEntity customer = new CustomerEntity("Harp", "Walter") + { + Email = "Walter@contoso.com", + PhoneNumber = "425-555-0101" + }; + + // Demonstrate how to Update the entity by changing the phone number + Console.WriteLine("2. Update an existing Entity using the InsertOrMerge Upsert Operation."); + customer.PhoneNumber = "425-555-0105"; + customer = await InsertOrMergeEntityAsync(table, customer); + + // Demonstrate how to Read the updated entity using a point query + Console.WriteLine("3. Reading the updated Entity."); + customer = await RetrieveEntityUsingPointQueryAsync(table, "Harp", "Walter"); + + // Demonstrate how to Delete an entity + Console.WriteLine("4. Delete the entity. "); + await DeleteEntityAsync(table, customer); + } + + /// + /// Demonstrate advanced table functionality including batch operations and segmented queries + /// + /// The sample table + private static async Task AdvancedTableOperationsAsync(CloudTable table) + { + // Demonstrate upsert and batch table operations + Console.WriteLine("4. Inserting a batch of entities. "); + await BatchInsertOfCustomerEntitiesAsync(table); + + // Query a range of data within a partition + Console.WriteLine("5. Retrieving entities with surname of Smith and first names >= 1 and <= 75"); + await PartitionRangeQueryAsync(table, "Smith", "0001", "0075"); + + // Query for all the data within a partition + Console.WriteLine("6. Retrieve entities with surname of Smith."); + await PartitionScanAsync(table, "Smith"); + } + + /// + /// Validate the connection string information in app.config and throws an exception if it looks like + /// the user hasn't updated this to valid values. + /// + /// Connection string for the storage service or the emulator + /// CloudStorageAccount object + private static CloudStorageAccount CreateStorageAccountFromConnectionString(string storageConnectionString) + { + CloudStorageAccount storageAccount; + try + { + storageAccount = CloudStorageAccount.Parse(storageConnectionString); + } + catch (FormatException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the application."); + throw; + } + catch (ArgumentException) + { + Console.WriteLine("Invalid storage account information provided. Please confirm the AccountName and AccountKey are valid in the app.config file - then restart the sample."); + Console.ReadLine(); + throw; + } + + return storageAccount; + } + + /// + /// The Table Service supports two main types of insert operations. + /// 1. Insert - insert a new entity. If an entity already exists with the same PK + RK an exception will be thrown. + /// 2. Replace - replace an existing entity. Replace an existing entity with a new entity. + /// 3. Insert or Replace - insert the entity if the entity does not exist, or if the entity exists, replace the existing one. + /// 4. Insert or Merge - insert the entity if the entity does not exist or, if the entity exists, merges the provided entity properties with the already existing ones. + /// + /// The sample table name + /// The entity to insert or merge + /// + private static async Task InsertOrMergeEntityAsync(CloudTable table, CustomerEntity entity) + { + if (entity == null) + { + throw new ArgumentNullException("entity"); + } + + // Create the InsertOrReplace TableOperation + TableOperation insertOrMergeOperation = TableOperation.InsertOrMerge(entity); + + // Execute the operation. + TableResult result = await table.ExecuteAsync(insertOrMergeOperation); + CustomerEntity insertedCustomer = result.Result as CustomerEntity; + return insertedCustomer; + } + + /// + /// Demonstrate the most efficient storage query - the point query - where both partition key and row key are specified. + /// + /// Sample table name + /// Partition key - ie - last name + /// Row key - ie - first name + private static async Task RetrieveEntityUsingPointQueryAsync(CloudTable table, string partitionKey, string rowKey) + { + TableOperation retrieveOperation = TableOperation.Retrieve(partitionKey, rowKey); + TableResult result = await table.ExecuteAsync(retrieveOperation); + CustomerEntity customer = result.Result as CustomerEntity; + if (customer != null) + { + Console.WriteLine("\t{0}\t{1}\t{2}\t{3}", customer.PartitionKey, customer.RowKey, customer.Email, customer.PhoneNumber); + } + + return customer; + } + + /// + /// Delete an entity + /// + /// Sample table name + /// Entity to delete + private static async Task DeleteEntityAsync(CloudTable table, CustomerEntity deleteEntity) + { + if (deleteEntity == null) + { + throw new ArgumentNullException("deleteEntity"); + } + + TableOperation deleteOperation = TableOperation.Delete(deleteEntity); + await table.ExecuteAsync(deleteOperation); + } + + /// + /// Demonstrate inserting of a large batch of entities. Some considerations for batch operations: + /// 1. You can perform updates, deletes, and inserts in the same single batch operation. + /// 2. A single batch operation can include up to 100 entities. + /// 3. All entities in a single batch operation must have the same partition key. + /// 4. While it is possible to perform a query as a batch operation, it must be the only operation in the batch. + /// 5. Batch size must be <= 4MB + /// + /// Sample table name + private static async Task BatchInsertOfCustomerEntitiesAsync(CloudTable table) + { + // Create the batch operation. + TableBatchOperation batchOperation = new TableBatchOperation(); + + // The following code generates test data for use during the query samples. + for (int i = 0; i < 100; i++) + { + batchOperation.InsertOrMerge(new CustomerEntity("Smith", string.Format("{0}", i.ToString("D4"))) + { + Email = string.Format("{0}@contoso.com", i.ToString("D4")), + PhoneNumber = string.Format("425-555-{0}", i.ToString("D4")) + }); + } + + // Execute the batch operation. + IList results = await table.ExecuteBatchAsync(batchOperation); + foreach (var res in results) + { + var customerInserted = res.Result as CustomerEntity; + Console.WriteLine("Inserted entity with\t Etag = {0} and PartitionKey = {1}, RowKey = {2}", customerInserted.ETag, customerInserted.PartitionKey, customerInserted.RowKey); + } + + } + + /// + /// Demonstrate a partition range query whereby we are searching within a partition for a set of entities that are within a + /// specific range. The async API's require the user to implement paging themselves using continuation tokens. + /// + /// Sample table name + /// The partition within which to search + /// The lowest bound of the row key range within which to search + /// The highest bound of the row key range within which to search + private static async Task PartitionRangeQueryAsync(CloudTable table, string partitionKey, string startRowKey, string endRowKey) + { + // Create the range query using the fluid API + TableQuery rangeQuery = new TableQuery().Where( + TableQuery.CombineFilters( + TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey), + TableOperators.And, + TableQuery.CombineFilters( + TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, startRowKey), + TableOperators.And, + TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThanOrEqual, endRowKey)))); + + // Page through the results - requesting 50 results at a time from the server. + TableContinuationToken token = null; + rangeQuery.TakeCount = 50; + do + { + TableQuerySegment segment = await table.ExecuteQuerySegmentedAsync(rangeQuery, token); + token = segment.ContinuationToken; + foreach (CustomerEntity entity in segment) + { + Console.WriteLine("Customer: {0},{1}\t{2}\t{3}", entity.PartitionKey, entity.RowKey, entity.Email, entity.PhoneNumber); + } + } + while (token != null); + } + + /// + /// Demonstrate a partition scan whereby we are searching for all the entities within a partition. Note this is not as efficient + /// as a range scan - but definitely more efficient than a full table scan. The async API's require the user to implement + /// paging themselves using continuation tokens. + /// + /// Sample table name + /// The partition within which to search + private static async Task PartitionScanAsync(CloudTable table, string partitionKey) + { + TableQuery partitionScanQuery = new TableQuery().Where + (TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey)); + + TableContinuationToken token = null; + // Page through the results + do + { + TableQuerySegment segment = await table.ExecuteQuerySegmentedAsync(partitionScanQuery, token); + token = segment.ContinuationToken; + foreach (CustomerEntity entity in segment) + { + Console.WriteLine("Customer: {0},{1}\t{2}\t{3}", entity.PartitionKey, entity.RowKey, entity.Email, entity.PhoneNumber); + } + } + while (token != null); + } + + /// + /// Delete a table + /// + /// Sample table name + private static async Task DeleteTableAsync(CloudTable table) + { + await table.DeleteIfExistsAsync(); + } + } +} diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/Properties/AssemblyInfo.cs b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..6eaeba6ee --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DataTableStorage")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DataTableStorage")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("89ee46c2-2cea-42b1-8bf0-9dc1843d19ef")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/_preprocess.xml b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/_preprocess.xml new file mode 100644 index 000000000..03de6171a --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/_preprocess.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/packages.config b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/packages.config new file mode 100644 index 000000000..5cdca6448 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/sw-file-icon.png b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/sw-file-icon.png new file mode 100644 index 000000000..03ff43202 Binary files /dev/null and b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/DataTableStorage/sw-file-icon.png differ diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/VisualStudioQuickStarts.csproj b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/VisualStudioQuickStarts.csproj new file mode 100644 index 000000000..314477e03 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/VisualStudioQuickStarts.csproj @@ -0,0 +1,45 @@ + + + + + Debug + AnyCPU + {4C38700A-3E8F-48B6-B352-541426499053} + Exe + Properties + VisualStudioQuickStarts + VisualStudioQuickStarts + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + \ No newline at end of file diff --git a/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/VisualStudioQuickStarts.sln b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/VisualStudioQuickStarts.sln new file mode 100644 index 000000000..9784ec024 --- /dev/null +++ b/Samples/GettingStarted/DotNet/VisualStudioQuickStarts/VisualStudioQuickStarts.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataBlobStorage", "DataBlobStorage\DataBlobStorage.csproj", "{85C81265-CA86-4C3D-93B9-7AC54FCD20AF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStorageQueue", "DataStorageQueue\DataStorageQueue.csproj", "{FB99BAF4-BE6E-43F2-B903-50F0049AC496}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataTableStorage", "DataTableStorage\DataTableStorage.csproj", "{058D9126-C9C7-4CB2-B38A-5849F7BFC32D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataFileStorage", "DataFileStorage\DataFileStorage.csproj", "{6498E995-C760-4920-8FF8-4A3EE3842D20}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {85C81265-CA86-4C3D-93B9-7AC54FCD20AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85C81265-CA86-4C3D-93B9-7AC54FCD20AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85C81265-CA86-4C3D-93B9-7AC54FCD20AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85C81265-CA86-4C3D-93B9-7AC54FCD20AF}.Release|Any CPU.Build.0 = Release|Any CPU + {FB99BAF4-BE6E-43F2-B903-50F0049AC496}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB99BAF4-BE6E-43F2-B903-50F0049AC496}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB99BAF4-BE6E-43F2-B903-50F0049AC496}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB99BAF4-BE6E-43F2-B903-50F0049AC496}.Release|Any CPU.Build.0 = Release|Any CPU + {058D9126-C9C7-4CB2-B38A-5849F7BFC32D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {058D9126-C9C7-4CB2-B38A-5849F7BFC32D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {058D9126-C9C7-4CB2-B38A-5849F7BFC32D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {058D9126-C9C7-4CB2-B38A-5849F7BFC32D}.Release|Any CPU.Build.0 = Release|Any CPU + {6498E995-C760-4920-8FF8-4A3EE3842D20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6498E995-C760-4920-8FF8-4A3EE3842D20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6498E995-C760-4920-8FF8-4A3EE3842D20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6498E995-C760-4920-8FF8-4A3EE3842D20}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json b/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json index bf3c04b3b..a7def9f41 100644 --- a/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json +++ b/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json @@ -1,36 +1,36 @@ { - "version": "4.4.1.0", - "dependencies": { - "xunit": "2.1.0-*", - "xunit.runner.dnx": "2.1.0-*", - "Microsoft.WindowsAzure.Storage": "", - "XUnitForMsTest": "" - }, + "version": "5.0.1.0", + "dependencies": { + "xunit": "2.1.0-*", + "xunit.runner.dnx": "2.1.0-*", + "Microsoft.WindowsAzure.Storage": "", + "XUnitForMsTest": "" + }, - "commands": { - "test": "xunit.runner.dnx" - }, + "commands": { + "test": "xunit.runner.dnx" + }, - "frameworks": { - "dnx451": { - "frameworkAssemblies": { - "System.Net.Http": "", - "System.ServiceModel": "", - "System.Linq": "" - } - }, - "dnxcore50": { - "dependencies": { - "System.Runtime": "4.0.20-beta-*" - } - } + "frameworks": { + "dnx451": { + "frameworkAssemblies": { + "System.Net.Http": "", + "System.ServiceModel": "", + "System.Linq": "" + } }, + "dnxcore50": { + "dependencies": { + "System.Runtime": "4.0.20-beta-*" + } + } + }, - "compilationOptions": { - "define": [ "ASPNET_K", "XUNIT" ] - }, + "compilationOptions": { + "define": [ "ASPNET_K", "XUNIT" ] + }, - "compile": [ + "compile": [ "**/*.cs", "../../Common/*.cs", "../../Common/Blob/*.cs", @@ -46,5 +46,5 @@ "../../WindowsRuntime/Queue/**/*.cs", "../../WindowsRuntime/File/**/*.cs", "../../WindowsRuntime/Table/**/*.cs" - ] + ] } diff --git a/Test/AspNet/XUnitForMsTest/project.json b/Test/AspNet/XUnitForMsTest/project.json index 4b3e22584..9207f8714 100644 --- a/Test/AspNet/XUnitForMsTest/project.json +++ b/Test/AspNet/XUnitForMsTest/project.json @@ -18,13 +18,13 @@ }, "dnxcore50": { "dependencies": { - "System.Runtime": "4.0.20-beta-*" + "System.Runtime": "4.0.10-beta-*" } } }, - "compilationOptions": { - "define": [ "XUNIT" ] - } + "compilationOptions": { + "define": [ "XUNIT" ] + } } diff --git a/Test/ClassLibraryCommon/Blob/BlobEncryptionTests.cs b/Test/ClassLibraryCommon/Blob/BlobEncryptionTests.cs index 232b24269..007d848f1 100644 --- a/Test/ClassLibraryCommon/Blob/BlobEncryptionTests.cs +++ b/Test/ClassLibraryCommon/Blob/BlobEncryptionTests.cs @@ -65,9 +65,11 @@ public void CloudBlobBasicEncryption() { this.DoCloudBlobEncryption(BlobType.BlockBlob, false); this.DoCloudBlobEncryption(BlobType.PageBlob, false); + this.DoCloudBlobEncryption(BlobType.AppendBlob, false); this.DoCloudBlobEncryption(BlobType.BlockBlob, true); this.DoCloudBlobEncryption(BlobType.PageBlob, true); + this.DoCloudBlobEncryption(BlobType.AppendBlob, true); } private void DoCloudBlobEncryption(BlobType type, bool partial) @@ -90,10 +92,14 @@ private void DoCloudBlobEncryption(BlobType type, bool partial) { blob = container.GetBlockBlobReference("blockblob"); } - else + else if (type == BlobType.PageBlob) { blob = container.GetPageBlobReference("pageblob"); } + else + { + blob = container.GetAppendBlobReference("appendblob"); + } // Create the Key to be used for wrapping. SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); @@ -155,9 +161,11 @@ public void CloudBlobBasicEncryptionAPM() { DoCloudBlobEncryptionAPM(BlobType.BlockBlob, false); DoCloudBlobEncryptionAPM(BlobType.PageBlob, false); + DoCloudBlobEncryptionAPM(BlobType.AppendBlob, false); DoCloudBlobEncryptionAPM(BlobType.BlockBlob, true); DoCloudBlobEncryptionAPM(BlobType.PageBlob, true); + DoCloudBlobEncryptionAPM(BlobType.AppendBlob, true); } private static void DoCloudBlobEncryptionAPM(BlobType type, bool partial) @@ -236,6 +244,186 @@ private static void DoCloudBlobEncryptionAPM(BlobType type, bool partial) } } + [TestMethod] + [Description("Upload and download encrypted blob from/to a file.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore)] + [TestCategory(TenantTypeCategory.DevFabric)] + [TestCategory(TenantTypeCategory.Cloud)] + public void CloudBlobEncryptionWithFile() + { + CloudBlobContainer container = GetRandomContainerReference(); + + try + { + container.Create(); + int size = 5 * 1024 * 1024; + byte[] buffer = GetRandomBuffer(size); + + CloudBlockBlob blob = container.GetBlockBlobReference("blockblob"); + + // Create the Key to be used for wrapping. + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + + // Create the resolver to be used for unwrapping. + DictionaryKeyResolver resolver = new DictionaryKeyResolver(); + resolver.Add(aesKey); + + // Create the encryption policy to be used for upload. + BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(aesKey, null); + + // Set the encryption policy on the request options. + BlobRequestOptions uploadOptions = new BlobRequestOptions() { EncryptionPolicy = uploadPolicy }; + + string inputFileName = Path.GetTempFileName(); + string outputFileName = Path.GetTempFileName(); + + using (FileStream file = new FileStream(inputFileName, FileMode.Create, FileAccess.Write)) + { + file.Write(buffer, 0, buffer.Length); + } + + // Upload the encrypted contents to the blob. + blob.UploadFromFile(inputFileName, FileMode.Open, null, uploadOptions, null); + + // Download the encrypted blob. + // Create the decryption policy to be used for download. There is no need to specify the + // key when the policy is only going to be used for downloads. Resolver is sufficient. + BlobEncryptionPolicy downloadPolicy = new BlobEncryptionPolicy(null, resolver); + + // Set the decryption policy on the request options. + BlobRequestOptions downloadOptions = new BlobRequestOptions() { EncryptionPolicy = downloadPolicy }; + + // Download and decrypt the encrypted contents from the blob. + blob.DownloadToFile(outputFileName, FileMode.Create, null, downloadOptions, null); + + // Compare that the decrypted contents match the input data. + using (FileStream inputFileStream = new FileStream(inputFileName, FileMode.Open, FileAccess.Read), + outputFileStream = new FileStream(outputFileName, FileMode.Open, FileAccess.Read)) + { + TestHelper.AssertStreamsAreEqual(inputFileStream, outputFileStream); + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Upload and download encrypted blob from/to a byte array.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore)] + [TestCategory(TenantTypeCategory.DevFabric)] + [TestCategory(TenantTypeCategory.Cloud)] + public void CloudBlobEncryptionWithByteArray() + { + CloudBlobContainer container = GetRandomContainerReference(); + + try + { + container.Create(); + int size = 5 * 1024 * 1024; + byte[] buffer = GetRandomBuffer(size); + byte[] outputBuffer = new byte[size]; + + CloudBlockBlob blob = container.GetBlockBlobReference("blockblob"); + + // Create the Key to be used for wrapping. + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + + // Create the resolver to be used for unwrapping. + DictionaryKeyResolver resolver = new DictionaryKeyResolver(); + resolver.Add(aesKey); + + // Create the encryption policy to be used for upload. + BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(aesKey, null); + + // Set the encryption policy on the request options. + BlobRequestOptions uploadOptions = new BlobRequestOptions() { EncryptionPolicy = uploadPolicy }; + + // Upload the encrypted contents to the blob. + blob.UploadFromByteArray(buffer, 0, buffer.Length, null, uploadOptions, null); + + // Download the encrypted blob. + // Create the decryption policy to be used for download. There is no need to specify the + // key when the policy is only going to be used for downloads. Resolver is sufficient. + BlobEncryptionPolicy downloadPolicy = new BlobEncryptionPolicy(null, resolver); + + // Set the decryption policy on the request options. + BlobRequestOptions downloadOptions = new BlobRequestOptions() { EncryptionPolicy = downloadPolicy }; + + // Download and decrypt the encrypted contents from the blob. + blob.DownloadToByteArray(outputBuffer, 0, null, downloadOptions, null); + + // Compare that the decrypted contents match the input data. + TestHelper.AssertBuffersAreEqual(buffer, outputBuffer); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Upload and download encrypted blob from/to text.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore)] + [TestCategory(TenantTypeCategory.DevFabric)] + [TestCategory(TenantTypeCategory.Cloud)] + public void CloudBlobEncryptionWithText() + { + CloudBlobContainer container = GetRandomContainerReference(); + + try + { + container.Create(); + string data = "String data"; + + CloudBlockBlob blob = container.GetBlockBlobReference("blockblob"); + + // Create the Key to be used for wrapping. + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + + // Create the resolver to be used for unwrapping. + DictionaryKeyResolver resolver = new DictionaryKeyResolver(); + resolver.Add(aesKey); + + // Create the encryption policy to be used for upload. + BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(aesKey, null); + + // Set the encryption policy on the request options. + BlobRequestOptions uploadOptions = new BlobRequestOptions() { EncryptionPolicy = uploadPolicy }; + + // Upload the encrypted contents to the blob. + blob.UploadText(data, null, null, uploadOptions, null); + + // Download the encrypted blob. + // Create the decryption policy to be used for download. There is no need to specify the + // key when the policy is only going to be used for downloads. Resolver is sufficient. + BlobEncryptionPolicy downloadPolicy = new BlobEncryptionPolicy(null, resolver); + + // Set the decryption policy on the request options. + BlobRequestOptions downloadOptions = new BlobRequestOptions() { EncryptionPolicy = downloadPolicy }; + + // Download and decrypt the encrypted contents from the blob. + string outputData = blob.DownloadText(null, null, downloadOptions, null); + + // Compare that the decrypted contents match the input data. + Assert.AreEqual(data, outputData); + } + finally + { + container.DeleteIfExists(); + } + } + [TestMethod] [Description("Validate AES and RSA key wrappers.")] [TestCategory(ComponentCategory.Blob)] @@ -498,6 +686,35 @@ public void CloudPageBlobEncryptionValidateRangeDecryption() this.ValidateRangeDecryption(BlobType.PageBlob, 1024, 0, 512); } + [TestMethod] + [Description("Validate range download of encrypted blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobEncryptionValidateRangeDecryption() + { + this.ValidateRangeDecryption(BlobType.AppendBlob, 2 * 512, 1 * 512, 1 * 512); + this.ValidateRangeDecryption(BlobType.AppendBlob, 2 * 512, null, null); + this.ValidateRangeDecryption(BlobType.AppendBlob, 2 * 512, 1 * 512, null); + this.ValidateRangeDecryption(BlobType.AppendBlob, 2 * 512, 0, 1 * 512); + this.ValidateRangeDecryption(BlobType.AppendBlob, 2 * 512, 4, 1 * 512); + this.ValidateRangeDecryption(BlobType.AppendBlob, 1325, 368, 495); + this.ValidateRangeDecryption(BlobType.AppendBlob, 1325, 369, 495); + + // Edge cases + this.ValidateRangeDecryption(BlobType.AppendBlob, 1024, 1023, 1); + this.ValidateRangeDecryption(BlobType.AppendBlob, 1024, 0, 1); + this.ValidateRangeDecryption(BlobType.AppendBlob, 1024, 512, 1); + this.ValidateRangeDecryption(BlobType.AppendBlob, 1024, 0, 512); + + // Check cases outside the blob size but within the padded size + this.ValidateRangeDecryption(BlobType.AppendBlob, 1025, 1023, 4, 2); + this.ValidateRangeDecryption(BlobType.AppendBlob, 1025, 1023, 16, 2); + this.ValidateRangeDecryption(BlobType.AppendBlob, 1025, 1023, 17, 2); + this.ValidateRangeDecryption(BlobType.AppendBlob, 1025, 1024, 16, 1); + } + private void ValidateRangeDecryption(BlobType type, int blobSize, int? blobOffset, int? length, int? verifyLength = null) { CloudBlobContainer container = GetRandomContainerReference(); @@ -565,6 +782,7 @@ public void BlobEncryptedWriteStreamTest() { DoBlobEncryptedWriteStreamTest(BlobType.BlockBlob); DoBlobEncryptedWriteStreamTest(BlobType.PageBlob); + DoBlobEncryptedWriteStreamTest(BlobType.AppendBlob); } private void DoBlobEncryptedWriteStreamTest(BlobType type) @@ -601,6 +819,12 @@ private void DoBlobEncryptedWriteStreamTest(BlobType type) blob.StreamWriteSizeInBytes = 16 * 1024; blobStream = ((CloudPageBlob)blob).OpenWrite(40 * 1024, null, uploadOptions, opContext); } + else + { + blob = container.GetAppendBlobReference("blob1"); + blob.StreamWriteSizeInBytes = 16 * 1024; + blobStream = ((CloudAppendBlob)blob).OpenWrite(true, null, uploadOptions, opContext); + } using (MemoryStream wholeBlob = new MemoryStream()) { @@ -612,7 +836,7 @@ private void DoBlobEncryptedWriteStreamTest(BlobType type) wholeBlob.Write(buffer, 0, buffer.Length); } - // Page blobs have one extra call due to create. + // Append and Page blobs have one extra call due to create. if (type == BlobType.BlockBlob) { Assert.AreEqual(1, opContext.RequestResults.Count); @@ -628,7 +852,7 @@ private void DoBlobEncryptedWriteStreamTest(BlobType type) blobStream.Write(buffer, 0, buffer.Length); wholeBlob.Write(buffer, 0, buffer.Length); - // Page blobs have one extra call due to create. + // Append and Page blobs have one extra call due to create. if (type == BlobType.BlockBlob) { Assert.AreEqual(2, opContext.RequestResults.Count); @@ -669,6 +893,7 @@ public void BlobEncryptedWriteStreamTestAPM() { DoBlobEncryptedWriteStreamTestAPM(BlobType.BlockBlob); DoBlobEncryptedWriteStreamTestAPM(BlobType.PageBlob); + DoBlobEncryptedWriteStreamTestAPM(BlobType.AppendBlob); } private void DoBlobEncryptedWriteStreamTestAPM(BlobType type) @@ -712,6 +937,14 @@ private void DoBlobEncryptedWriteStreamTestAPM(BlobType type) waitHandle.WaitOne(); blobStream = ((CloudPageBlob)blob).EndOpenWrite(result); } + else + { + blob = container.GetAppendBlobReference("blob1"); + blob.StreamWriteSizeInBytes = 16 * 1024; + result = ((CloudAppendBlob)blob).BeginOpenWrite(true, null, uploadOptions, opContext, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blobStream = ((CloudAppendBlob)blob).EndOpenWrite(result); + } } using (MemoryStream wholeBlob = new MemoryStream()) @@ -734,7 +967,7 @@ private void DoBlobEncryptedWriteStreamTestAPM(BlobType type) wholeBlob.Write(buffer, 0, buffer.Length); } - // Page blobs have one extra call due to create. + // Append and Page blobs have one extra call due to create. if (type == BlobType.BlockBlob) { Assert.AreEqual(1, opContext.RequestResults.Count); @@ -764,7 +997,7 @@ private void DoBlobEncryptedWriteStreamTestAPM(BlobType type) blobStream.EndWrite(result); wholeBlob.Write(buffer, 0, buffer.Length); - // Page blobs have one extra call due to create. + // Append and Page blobs have one extra call due to create. if (type == BlobType.BlockBlob) { Assert.AreEqual(2, opContext.RequestResults.Count); @@ -829,6 +1062,11 @@ public void BlobUpdateShouldThrowWithEncryption() () => blockBlob.PutBlock(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), stream, null, null, uploadOptions, null), "PutBlock does not support encryption."); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("appendblob"); + TestHelper.ExpectedException( + () => appendBlob.AppendBlock(stream, null, null, uploadOptions, null), + "AppendBlock does not support encryption."); + CloudPageBlob pageBlob = container.GetPageBlobReference("pageblob"); TestHelper.ExpectedException( () => pageBlob.WritePages(stream, 0, null, null, uploadOptions, null), @@ -845,6 +1083,237 @@ public void BlobUpdateShouldThrowWithEncryption() } } + [TestMethod] + [Description("Validate that default request options correctly set encryption policy.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void BlobUploadWorksWithDefaultRequestOptions() + { + CloudBlobContainer container = GetRandomContainerReference(); + + byte[] buffer = GetRandomBuffer(16 * 1024); + + // Create the Key to be used for wrapping. + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + + // Create the encryption policy to be used for upload. + BlobEncryptionPolicy policy = new BlobEncryptionPolicy(aesKey, null); + + // Set the encryption policy on the request options. + BlobRequestOptions options = new BlobRequestOptions() { EncryptionPolicy = policy }; + + // Set default request options + container.ServiceClient.DefaultRequestOptions = options; + + try + { + container.Create(); + + using (MemoryStream stream = new MemoryStream(buffer)) + { + CloudBlockBlob blockBlob = container.GetBlockBlobReference("blockblob"); + blockBlob.UploadFromStream(stream, buffer.Length); + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Validate encryption/decryption with RequireEncryption flag.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore)] + [TestCategory(TenantTypeCategory.DevFabric)] + [TestCategory(TenantTypeCategory.Cloud)] + public void CloudBlobEncryptionWithStrictMode() + { + this.DoCloudBlobEncryptionWithStrictMode(BlobType.BlockBlob); + this.DoCloudBlobEncryptionWithStrictMode(BlobType.PageBlob); + } + + private void DoCloudBlobEncryptionWithStrictMode(BlobType type) + { + CloudBlobContainer container = GetRandomContainerReference(); + + try + { + container.Create(); + int size = 5 * 1024 * 1024; + byte[] buffer = GetRandomBuffer(size); + + ICloudBlob blob; + + if (type == BlobType.BlockBlob) + { + blob = container.GetBlockBlobReference("blob1"); + } + else + { + blob = container.GetPageBlobReference("blob1"); + } + + // Create the Key to be used for wrapping. + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + + // Create the resolver to be used for unwrapping. + DictionaryKeyResolver resolver = new DictionaryKeyResolver(); + resolver.Add(aesKey); + + // Create the encryption policy to be used for upload. + BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(aesKey, null); + + // Set the encryption policy on the request options. + BlobRequestOptions uploadOptions = new BlobRequestOptions() { EncryptionPolicy = uploadPolicy }; + + // Set RequireEncryption flag to true. + uploadOptions.RequireEncryption = true; + + // Upload an encrypted blob with the policy set. + MemoryStream stream = new MemoryStream(buffer); + blob.UploadFromStream(stream, size, null, uploadOptions, null); + + // Upload the blob when RequireEncryption is true and no policy is set. This should throw an error. + uploadOptions.EncryptionPolicy = null; + + stream = new MemoryStream(buffer); + TestHelper.ExpectedException( + () => blob.UploadFromStream(stream, size, null, uploadOptions, null), + "Not specifying a policy when RequireEnryption is set to true should throw."); + + // Create the encryption policy to be used for download. + BlobEncryptionPolicy downloadPolicy = new BlobEncryptionPolicy(null, resolver); + + // Set the encryption policy on the request options. + BlobRequestOptions downloadOptions = new BlobRequestOptions() { EncryptionPolicy = downloadPolicy }; + + // Set RequireEncryption flag to true. + downloadOptions.RequireEncryption = true; + + // Download the encrypted blob. + MemoryStream outputStream = new MemoryStream(); + blob.DownloadToStream(outputStream, null, downloadOptions, null); + + blob.Metadata.Clear(); + + // Upload a plain text blob. + stream = new MemoryStream(buffer); + blob.UploadFromStream(stream, size); + + // Try to download an encrypted blob with RequireEncryption set to true. This should throw. + outputStream = new MemoryStream(); + TestHelper.ExpectedException( + () => blob.DownloadToStream(outputStream, null, downloadOptions, null), + "Downloading with RequireEncryption set to true and no metadata on the service should fail."); + + // Set RequireEncryption to false and download. + downloadOptions.RequireEncryption = false; + blob.DownloadToStream(outputStream, null, downloadOptions, null); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Validate partial blob encryption with RequireEncryption flag.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudBlobEncryptionWithStrictModeOnPartialBlob() + { + CloudBlobContainer container = GetRandomContainerReference(); + + int size = 5 * 1024 * 1024; + byte[] buffer = GetRandomBuffer(size); + + ICloudBlob blob; + MemoryStream stream = new MemoryStream(buffer); + String blockId = Convert.ToBase64String(Guid.NewGuid().ToByteArray()); + + BlobRequestOptions options = new BlobRequestOptions() + { + RequireEncryption = true + }; + + blob = container.GetBlockBlobReference("blob1"); + try + { + ((CloudBlockBlob)blob).PutBlock(blockId, stream, null, null, options, null); + Assert.Fail("PutBlock with RequireEncryption on should fail."); + } + catch (InvalidOperationException ex) + { + Assert.AreEqual(ex.Message, SR.EncryptionPolicyMissingInStrictMode); + } + + blob = container.GetPageBlobReference("blob1"); + try + { + ((CloudPageBlob)blob).WritePages(stream, 0, null, null, options, null); + Assert.Fail("WritePages with RequireEncryption on should fail."); + } + catch (InvalidOperationException ex) + { + Assert.AreEqual(ex.Message, SR.EncryptionPolicyMissingInStrictMode); + } + + blob = container.GetAppendBlobReference("blob1"); + try + { + ((CloudAppendBlob)blob).AppendBlock(stream, null, null, options, null); + Assert.Fail("AppendBlock with RequireEncryption on should fail."); + } + catch (InvalidOperationException ex) + { + Assert.AreEqual(ex.Message, SR.EncryptionPolicyMissingInStrictMode); + } + + // Create the Key to be used for wrapping. + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + options.EncryptionPolicy = new BlobEncryptionPolicy(aesKey, null); + + blob = container.GetBlockBlobReference("blob1"); + try + { + ((CloudBlockBlob)blob).PutBlock(blockId, stream, null, null, options, null); + Assert.Fail("PutBlock with an EncryptionPolicy should fail."); + } + catch (InvalidOperationException ex) + { + Assert.AreEqual(ex.Message, SR.EncryptionNotSupportedForOperation); + } + + blob = container.GetPageBlobReference("blob1"); + try + { + ((CloudPageBlob)blob).WritePages(stream, 0, null, null, options, null); + Assert.Fail("WritePages with an EncryptionPolicy should fail."); + } + catch (InvalidOperationException ex) + { + Assert.AreEqual(ex.Message, SR.EncryptionNotSupportedForOperation); + } + + blob = container.GetAppendBlobReference("blob1"); + try + { + ((CloudAppendBlob)blob).AppendBlock(stream, null, null, options, null); + Assert.Fail("AppendBlock with an EncryptionPolicy should fail."); + } + catch (InvalidOperationException ex) + { + Assert.AreEqual(ex.Message, SR.EncryptionNotSupportedForOperation); + } + } + private static ICloudBlob GetCloudBlobReference(BlobType type, CloudBlobContainer container) { ICloudBlob blob; @@ -852,10 +1321,14 @@ private static ICloudBlob GetCloudBlobReference(BlobType type, CloudBlobContaine { blob = container.GetBlockBlobReference("blockblob"); } - else + else if (type == BlobType.PageBlob) { blob = container.GetPageBlobReference("pageblob"); } + else + { + blob = container.GetAppendBlobReference("appendblob"); + } return blob; } diff --git a/Test/ClassLibraryCommon/Blob/BlobReadStreamTest.cs b/Test/ClassLibraryCommon/Blob/BlobReadStreamTest.cs index bebba4478..69a58b3b7 100644 --- a/Test/ClassLibraryCommon/Blob/BlobReadStreamTest.cs +++ b/Test/ClassLibraryCommon/Blob/BlobReadStreamTest.cs @@ -89,6 +89,27 @@ public void PageBlobReadStreamReadSizeTest() } } + [TestMethod] + [Description("Validate StreamMinimumReadSizeInBytes property")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobReadStreamReadSizeTest() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + BlobReadStreamReadSizeTest(blob); + } + finally + { + container.DeleteIfExists(); + } + } + private void BlobReadStreamReadSizeTest(ICloudBlob blob) { byte[] buffer = GetRandomBuffer(5 * 1024 * 1024); @@ -179,6 +200,27 @@ public void PageBlobReadStreamReadSizeTestAPM() } } + [TestMethod] + [Description("Validate StreamMinimumReadSizeInBytes property")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobReadStreamReadSizeTestAPM() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + BlobReadStreamReadSizeTestAPM(blob); + } + finally + { + container.DeleteIfExists(); + } + } + private void BlobReadStreamReadSizeTestAPM(ICloudBlob blob) { IAsyncResult result; @@ -319,6 +361,41 @@ public void PageBlobReadStreamBasicTest() } } + [TestMethod] + [Description("Download a blob using CloudBlobStream")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobReadStreamBasicTest() + { + byte[] buffer = GetRandomBuffer(4 * 1024 * 1024); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + blob.CreateOrReplace(); + blob.AppendBlock(wholeBlob, null); + } + + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + using (Stream blobStream = blob.OpenRead()) + { + TestHelper.AssertStreamsAreEqual(wholeBlob, blobStream); + } + } + } + finally + { + container.DeleteIfExists(); + } + } + [TestMethod] [Description("Modify a blob while downloading it using CloudBlobStream")] [TestCategory(ComponentCategory.Blob)] @@ -689,6 +766,195 @@ public void PageBlobReadLockToETagTestTask() } #endif + + [TestMethod] + [Description("Modify a blob while downloading it using CloudBlobStream")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobReadLockToETagTest() + { + byte[] outBuffer = new byte[1 * 1024 * 1024]; + byte[] buffer = GetRandomBuffer(2 * outBuffer.Length); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamMinimumReadSizeInBytes = outBuffer.Length; + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + blob.CreateOrReplace(); + blob.AppendBlock(wholeBlob, null); + } + + using (Stream blobStream = blob.OpenRead()) + { + blobStream.Read(outBuffer, 0, outBuffer.Length); + blob.SetMetadata(); + TestHelper.ExpectedException( + () => blobStream.Read(outBuffer, 0, outBuffer.Length), + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + + using (Stream blobStream = blob.OpenRead()) + { + long length = blobStream.Length; + blob.SetMetadata(); + TestHelper.ExpectedException( + () => blobStream.Read(outBuffer, 0, outBuffer.Length), + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + + AccessCondition accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(DateTimeOffset.Now.Subtract(TimeSpan.FromHours(1))); + blob.SetMetadata(); + TestHelper.ExpectedException( + () => blob.OpenRead(accessCondition), + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Modify a blob while downloading it using CloudBlobStream")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobReadLockToETagTestAPM() + { + byte[] outBuffer = new byte[1 * 1024 * 1024]; + byte[] buffer = GetRandomBuffer(2 * outBuffer.Length); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamMinimumReadSizeInBytes = outBuffer.Length; + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + blob.CreateOrReplace(); + blob.AppendBlock(wholeBlob, null); + } + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = blob.BeginOpenRead( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + using (Stream blobStream = blob.EndOpenRead(result)) + { + blobStream.Read(outBuffer, 0, outBuffer.Length); + blob.SetMetadata(); + TestHelper.ExpectedException( + () => blobStream.Read(outBuffer, 0, outBuffer.Length), + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + + result = blob.BeginOpenRead( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + using (Stream blobStream = blob.EndOpenRead(result)) + { + long length = blobStream.Length; + blob.SetMetadata(); + TestHelper.ExpectedException( + () => blobStream.Read(outBuffer, 0, outBuffer.Length), + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + + AccessCondition accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(DateTimeOffset.Now.Subtract(TimeSpan.FromHours(1))); + blob.SetMetadata(); + result = blob.BeginOpenRead( + accessCondition, + null, + null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + TestHelper.ExpectedException( + () => blob.EndOpenRead(result), + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + } + finally + { + container.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Modify a blob while downloading it using CloudBlobStream")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobReadLockToETagTestTask() + { + byte[] outBuffer = new byte[1 * 1024 * 1024]; + byte[] buffer = GetRandomBuffer(2 * outBuffer.Length); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.CreateAsync().Wait(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamMinimumReadSizeInBytes = outBuffer.Length; + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + blob.CreateOrReplace(); + blob.AppendBlock(wholeBlob, null); + } + + using (Stream blobStream = blob.OpenReadAsync().Result) + { + blobStream.Read(outBuffer, 0, outBuffer.Length); + blob.SetMetadataAsync().Wait(); + TestHelper.ExpectedException( + () => blobStream.Read(outBuffer, 0, outBuffer.Length), + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + + using (Stream blobStream = blob.OpenReadAsync().Result) + { + long length = blobStream.Length; + blob.SetMetadataAsync().Wait(); + TestHelper.ExpectedException( + () => blobStream.Read(outBuffer, 0, outBuffer.Length), + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + + AccessCondition accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(DateTimeOffset.Now.Subtract(TimeSpan.FromHours(1))); + blob.SetMetadataAsync().Wait(); + TestHelper.ExpectedExceptionTask( + blob.OpenReadAsync(accessCondition, null, null), + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } +#endif + private static int BlobReadStreamSeekAndCompare(Stream blobStream, byte[] bufferToCompare, long offset, int readSize, int expectedReadCount, bool isAsync) { byte[] testBuffer = new byte[readSize]; @@ -851,5 +1117,47 @@ public void PageBlobReadStreamSeekTest() container.DeleteIfExists(); } } + + [TestMethod] + [Description("Seek and read in a CloudBlobStream")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobReadStreamSeekTest() + { + byte[] buffer = GetRandomBuffer(3 * 1024 * 1024); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamMinimumReadSizeInBytes = 2 * 1024 * 1024; + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + blob.CreateOrReplace(); + blob.AppendBlock(wholeBlob, null); + } + + OperationContext opContext = new OperationContext(); + using (Stream blobStream = blob.OpenRead(null, null, opContext)) + { + int attempts = BlobReadStreamSeekTest(blobStream, blob.StreamMinimumReadSizeInBytes, buffer, false); + TestHelper.AssertNAttempts(opContext, attempts); + } + + opContext = new OperationContext(); + using (Stream blobStream = blob.OpenRead(null, null, opContext)) + { + int attempts = BlobReadStreamSeekTest(blobStream, blob.StreamMinimumReadSizeInBytes, buffer, true); + TestHelper.AssertNAttempts(opContext, attempts); + } + } + finally + { + container.DeleteIfExists(); + } + } } } diff --git a/Test/ClassLibraryCommon/Blob/BlobTestBase.cs b/Test/ClassLibraryCommon/Blob/BlobTestBase.cs index 146bae0e3..30538e51a 100644 --- a/Test/ClassLibraryCommon/Blob/BlobTestBase.cs +++ b/Test/ClassLibraryCommon/Blob/BlobTestBase.cs @@ -70,6 +70,13 @@ public static List CreateBlobs(CloudBlobContainer container, int count, pageBlob.Create(0); blobs.Add(name); break; + + case BlobType.AppendBlob: + name = "ab" + Guid.NewGuid().ToString(); + CloudAppendBlob appendBlob = container.GetAppendBlobReference(name); + appendBlob.CreateOrReplace(); + blobs.Add(name); + break; } } return blobs; @@ -97,6 +104,13 @@ public static List CreateBlobsTask(CloudBlobContainer container, int cou pageBlob.CreateAsync(0).Wait(); blobs.Add(name); break; + + case BlobType.AppendBlob: + name = "ab" + Guid.NewGuid().ToString(); + CloudAppendBlob appendBlob = container.GetAppendBlobReference(name); + appendBlob.CreateOrReplaceAsync().Wait(); + blobs.Add(name); + break; } } return blobs; @@ -118,10 +132,18 @@ public static void UploadText(CloudBlob blob, string text, Encoding encoding, Ac stream.Write(padding, 0, padding.Length); } } + stream.Seek(0, SeekOrigin.Begin); blob.ServiceClient.DefaultRequestOptions.ParallelOperationThreadCount = 2; - if (blob.BlobType == BlobType.PageBlob) + + if (blob.BlobType == BlobType.AppendBlob) + { + CloudAppendBlob blob1 = blob as CloudAppendBlob; + blob1.CreateOrReplace(); + blob1.AppendBlock(stream, null); + } + else if (blob.BlobType == BlobType.PageBlob) { CloudPageBlob pageBlob = blob as CloudPageBlob; pageBlob.UploadFromStream(stream, accessCondition, options, operationContext); @@ -152,24 +174,40 @@ public static void UploadTextAPM(CloudBlob blob, string text, Encoding encoding, stream.Seek(0, SeekOrigin.Begin); blob.ServiceClient.DefaultRequestOptions.ParallelOperationThreadCount = 2; + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) { - IAsyncResult result; - if (blob.BlobType == BlobType.PageBlob) + if (blob.BlobType == BlobType.AppendBlob) + { + CloudAppendBlob blob1 = blob as CloudAppendBlob; + + IAsyncResult result = blob1.BeginCreateOrReplace( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob1.EndCreateOrReplace(result); + + result = blob1.BeginAppendBlock(stream, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob1.EndAppendBlock(result); + } + else if (blob.BlobType == BlobType.PageBlob) { CloudPageBlob pageBlob = blob as CloudPageBlob; - result = pageBlob.BeginUploadFromStream(stream, accessCondition, options, operationContext, - ar => waitHandle.Set(), - null); + IAsyncResult result = pageBlob.BeginUploadFromStream(stream, accessCondition, options, operationContext, + ar => waitHandle.Set(), + null); waitHandle.WaitOne(); pageBlob.EndUploadFromStream(result); } else { CloudBlockBlob blockBlob = blob as CloudBlockBlob; - result = blockBlob.BeginUploadFromStream(stream, accessCondition, options, operationContext, - ar => waitHandle.Set(), - null); + IAsyncResult result = blockBlob.BeginUploadFromStream(stream, accessCondition, options, operationContext, + ar => waitHandle.Set(), + null); waitHandle.WaitOne(); blockBlob.EndUploadFromStream(result); } @@ -196,9 +234,16 @@ public static void UploadTextTask(CloudBlob blob, string text, Encoding encoding stream.Seek(0, SeekOrigin.Begin); blob.ServiceClient.DefaultRequestOptions.ParallelOperationThreadCount = 2; + try { - if (blob.BlobType == BlobType.PageBlob) + if (blob.BlobType == BlobType.AppendBlob) + { + CloudAppendBlob blob1 = blob as CloudAppendBlob; + blob1.CreateOrReplaceAsync().Wait(); + blob1.AppendBlock(stream, null); + } + else if (blob.BlobType == BlobType.PageBlob) { CloudPageBlob pageBlob = blob as CloudPageBlob; pageBlob.UploadFromStreamAsync(stream, accessCondition, options, operationContext).Wait(); diff --git a/Test/ClassLibraryCommon/Blob/BlobUploadDownloadTest.cs b/Test/ClassLibraryCommon/Blob/BlobUploadDownloadTest.cs index b55ed5409..64744aa37 100644 --- a/Test/ClassLibraryCommon/Blob/BlobUploadDownloadTest.cs +++ b/Test/ClassLibraryCommon/Blob/BlobUploadDownloadTest.cs @@ -22,6 +22,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob using System.Collections.Generic; using System.IO; using System.Net; + using System.Security.Cryptography; using System.Threading; [TestClass] @@ -52,7 +53,7 @@ public void TestCleanup() } [TestMethod] - [Description("Download a specific range of the blob to a stream")] + [Description("Download a specific range of a page blob to a stream")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -90,7 +91,46 @@ public void PageBlobDownloadToStreamRangeTest() } [TestMethod] - [Description("Upload a stream to a blob")] + [Description("Download a specific range of an append blob to a stream")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobDownloadToStreamRangeTest() + { + byte[] buffer = GetRandomBuffer(2 * 1024); + + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + blob.CreateOrReplace(); + blob.AppendBlock(wholeBlob, null); + + byte[] testBuffer = new byte[1024]; + MemoryStream blobStream = new MemoryStream(testBuffer); + StorageException storageEx = TestHelper.ExpectedException( + () => blob.DownloadRangeToStream(blobStream, 0, 0), + "Requesting 0 bytes when downloading range should not work"); + Assert.IsInstanceOfType(storageEx.InnerException, typeof(ArgumentOutOfRangeException)); + blob.DownloadRangeToStream(blobStream, 0, 1024); + Assert.AreEqual(blobStream.Position, 1024); + TestHelper.AssertStreamsAreEqualAtIndex(blobStream, wholeBlob, 0, 0, 1024); + + CloudAppendBlob blob2 = this.testContainer.GetAppendBlobReference("blob1"); + MemoryStream blobStream2 = new MemoryStream(testBuffer); + storageEx = TestHelper.ExpectedException( + () => blob2.DownloadRangeToStream(blobStream, 1024, 0), + "Requesting 0 bytes when downloading range should not work"); + Assert.IsInstanceOfType(storageEx.InnerException, typeof(ArgumentOutOfRangeException)); + blob2.DownloadRangeToStream(blobStream2, 1024, 1024); + TestHelper.AssertStreamsAreEqualAtIndex(blobStream2, wholeBlob, 0, 1024, 1024); + + AssertAreEqual(blob, blob2); + } + } + + [TestMethod] + [Description("Upload a stream to a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -111,7 +151,7 @@ public void BlobUploadFromStreamTest() } [TestMethod] - [Description("Upload from text to a blob")] + [Description("Upload from text to a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -147,7 +187,45 @@ public void BlobUploadWithoutMD5ValidationAndStoreBlobContentTest() } [TestMethod] - [Description("Upload from text to a blob")] + [Description("Upload from text to an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobUploadWithMD5ValidationTest() + { + byte[] buffer = GetRandomBuffer(2 * 1024); + MD5 md5 = MD5.Create(); + string contentMD5 = Convert.ToBase64String(md5.ComputeHash(buffer)); + + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + BlobRequestOptions options = new BlobRequestOptions(); + options.DisableContentMD5Validation = false; + options.StoreBlobContentMD5 = false; + OperationContext context = new OperationContext(); + using (MemoryStream srcStream = new MemoryStream(buffer)) + { + blob.CreateOrReplace(); + blob.AppendBlock(srcStream, contentMD5, null, options, context); + + blob.Properties.ContentMD5 = "MDAwMDAwMDA="; + blob.SetProperties(null, options, context); + byte[] testBuffer = new byte[2048]; + MemoryStream dstStream = new MemoryStream(testBuffer); + TestHelper.ExpectedException(() => blob.DownloadRangeToStream(dstStream, null, null, null, options, context), + "Try to Download a stream with a corrupted md5 and DisableMD5Validation set to false", + HttpStatusCode.OK); + + options.DisableContentMD5Validation = true; + blob.SetProperties(null, options, context); + byte[] testBuffer2 = new byte[2048]; + MemoryStream dstStream2 = new MemoryStream(testBuffer2); + blob.DownloadRangeToStream(dstStream2, null, null, null, options, context); + } + } + + [TestMethod] + [Description("Check that the empty header is not used for signing.")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -178,7 +256,7 @@ public void BlobEmptyHeaderSigningTest() } [TestMethod] - [Description("Upload from file to a blob")] + [Description("Upload from file to a block blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -213,7 +291,7 @@ public void CloudBlockBlobUploadDownloadFile() } [TestMethod] - [Description("Upload from file to a blob")] + [Description("Upload from file to a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -251,7 +329,42 @@ public void CloudPageBlobUploadDownloadFile() } [TestMethod] - [Description("Upload from file to a blob")] + [Description("Upload from file to an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadDownloadFile() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + CloudAppendBlob nullBlob = this.testContainer.GetAppendBlobReference("null"); + this.DoUploadDownloadFile(blob, 0, false); + this.DoUploadDownloadFile(blob, 4096, false); + this.DoUploadDownloadFile(blob, 4097, false); + + TestHelper.ExpectedException( + () => blob.UploadFromFile("non_existent.file", FileMode.Open), + "UploadFromFile requires an existing file"); + + TestHelper.ExpectedException( + () => nullBlob.DownloadToFile("garbage.file", FileMode.Create), + "DownloadToFile should not leave an empty file behind after failing."); + Assert.IsFalse(File.Exists("garbage.file")); + + TestHelper.ExpectedException( + () => nullBlob.DownloadToFile("garbage.file", FileMode.CreateNew), + "DownloadToFile should not leave an empty file behind after failing."); + Assert.IsFalse(File.Exists("garbage.file")); + + TestHelper.ExpectedException( + () => nullBlob.DownloadToFile("garbage.file", FileMode.Append), + "DownloadToFile should leave an empty file behind after failing depending on file mode."); + Assert.IsTrue(File.Exists("garbage.file")); + File.Delete("garbage.file"); + } + + [TestMethod] + [Description("Upload from file to a block blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -305,7 +418,7 @@ public void CloudBlockBlobUploadDownloadFileAPM() } [TestMethod] - [Description("Upload from file to a blob")] + [Description("Upload from file to a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -361,9 +474,63 @@ public void CloudPageBlobUploadDownloadFileAPM() } } + [TestMethod] + [Description("Upload from file to an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadDownloadFileAPM() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + CloudBlockBlob nullBlob = this.testContainer.GetBlockBlobReference("null"); + this.DoUploadDownloadFile(blob, 0, true); + this.DoUploadDownloadFile(blob, 4096, true); + this.DoUploadDownloadFile(blob, 4097, true); + + TestHelper.ExpectedException( + () => blob.BeginUploadFromFile("non_existent.file", FileMode.Open, null, null), + "UploadFromFile requires an existing file"); + + IAsyncResult result; + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + OperationContext context = new OperationContext(); + result = nullBlob.BeginDownloadToFile("garbage.file", FileMode.Create, null, null, context, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + TestHelper.ExpectedException( + () => nullBlob.EndDownloadToFile(result), + "DownloadToFile should not leave an empty file behind after failing."); + Assert.IsFalse(File.Exists("garbage.file")); + + context = new OperationContext(); + result = nullBlob.BeginDownloadToFile("garbage.file", FileMode.CreateNew, null, null, context, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + TestHelper.ExpectedException( + () => nullBlob.EndDownloadToFile(result), + "DownloadToFile should not leave an empty file behind after failing."); + Assert.IsFalse(File.Exists("garbage.file")); + + context = new OperationContext(); + result = nullBlob.BeginDownloadToFile("garbage.file", FileMode.Append, null, null, context, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + TestHelper.ExpectedException( + () => nullBlob.EndDownloadToFile(result), + "DownloadToFile should leave an empty file behind after failing, depending on file mode."); + Assert.IsTrue(File.Exists("garbage.file")); + File.Delete("garbage.file"); + } + } + #if TASK [TestMethod] - [Description("Upload from file to a blob")] + [Description("Upload from file to a block blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -401,7 +568,7 @@ public void CloudBlockBlobUploadDownloadFileTask() } [TestMethod] - [Description("Upload from file to a blob")] + [Description("Upload from file to a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -441,6 +608,44 @@ public void CloudPageBlobUploadDownloadFileTask() File.Delete("garbage.file"); } + [TestMethod] + [Description("Upload from file to an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadDownloadFileTask() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + CloudAppendBlob nullBlob = this.testContainer.GetAppendBlobReference("null"); + this.DoUploadDownloadFileTask(blob, 0); + this.DoUploadDownloadFileTask(blob, 4096); + this.DoUploadDownloadFileTask(blob, 4097); + + TestHelper.ExpectedException( + () => blob.UploadFromFileAsync("non_existent.file", FileMode.Open), + "UploadFromFile requires an existing file"); + + AggregateException e = TestHelper.ExpectedException( + () => nullBlob.DownloadToFileAsync("garbage.file", FileMode.Create).Wait(), + "DownloadToFile should not leave an empty file behind after failing."); + Assert.IsTrue(e.InnerException is StorageException); + Assert.IsFalse(File.Exists("garbage.file")); + + e = TestHelper.ExpectedException( + () => nullBlob.DownloadToFileAsync("garbage.file", FileMode.CreateNew).Wait(), + "DownloadToFile should not leave an empty file behind after failing."); + Assert.IsTrue(e.InnerException is StorageException); + Assert.IsFalse(File.Exists("garbage.file")); + + e = TestHelper.ExpectedException( + () => nullBlob.DownloadToFileAsync("garbage.file", FileMode.OpenOrCreate).Wait(), + "DownloadToFile should leave an empty file behind after failing depending on file mode."); + Assert.IsTrue(e.InnerException is StorageException); + Assert.IsTrue(File.Exists("garbage.file")); + File.Delete("garbage.file"); + } + private void DoUploadDownloadFileTask(ICloudBlob blob, int fileSize) { string inputFileName = Path.GetTempFileName(); @@ -454,9 +659,9 @@ private void DoUploadDownloadFileTask(ICloudBlob blob, int fileSize) file.Write(buffer, 0, buffer.Length); } + OperationContext context = new OperationContext(); blob.UploadFromFileAsync(inputFileName, FileMode.Open).Wait(); - OperationContext context = new OperationContext(); blob.UploadFromFileAsync(inputFileName, FileMode.Open, null, null, context).Wait(); Assert.IsNotNull(context.LastResult.ServiceRequestID); @@ -499,7 +704,7 @@ private void DoUploadDownloadFileTask(ICloudBlob blob, int fileSize) { if (ex.InnerException != null) { - throw ex.InnerException; + throw ex.InnerException; } throw; @@ -530,13 +735,13 @@ private void DoUploadDownloadFile(ICloudBlob blob, int fileSize, bool isAsync) IAsyncResult result; using (AutoResetEvent waitHandle = new AutoResetEvent(false)) { + OperationContext context = new OperationContext(); result = blob.BeginUploadFromFile(inputFileName, FileMode.Open, - ar => waitHandle.Set(), - null); + ar => waitHandle.Set(), + null); waitHandle.WaitOne(); blob.EndUploadFromFile(result); - OperationContext context = new OperationContext(); result = blob.BeginUploadFromFile(inputFileName, FileMode.Open, null, null, context, ar => waitHandle.Set(), null); @@ -588,9 +793,9 @@ private void DoUploadDownloadFile(ICloudBlob blob, int fileSize, bool isAsync) } else { - blob.UploadFromFile(inputFileName, FileMode.Open); - OperationContext context = new OperationContext(); + + blob.UploadFromFile(inputFileName, FileMode.Open); blob.UploadFromFile(inputFileName, FileMode.Open, null, null, context); Assert.IsNotNull(context.LastResult.ServiceRequestID); @@ -636,7 +841,7 @@ private void DoUploadDownloadFile(ICloudBlob blob, int fileSize, bool isAsync) } [TestMethod] - [Description("Upload a blob using a byte array")] + [Description("Upload a block blob using a byte array")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -652,7 +857,7 @@ public void CloudBlockBlobUploadFromByteArray() } [TestMethod] - [Description("Upload a blob using a byte array")] + [Description("Upload a block blob using a byte array")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -668,7 +873,7 @@ public void CloudBlockBlobUploadFromByteArrayAPM() } [TestMethod] - [Description("Upload a blob using a byte array")] + [Description("Upload a page blob using a byte array")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -687,7 +892,7 @@ public void CloudPageBlobUploadFromByteArray() } [TestMethod] - [Description("Upload a blob using a byte array")] + [Description("Upload a page blob using a byte array")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -705,9 +910,73 @@ public void CloudPageBlobUploadFromByteArrayAPM() "Page blobs must be 512-byte aligned"); } + [TestMethod] + [Description("Upload an append blob using a byte array")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadFromByteArray() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 0, 4 * 512, false); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 0, 2 * 512, false); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 1 * 512, 2 * 512, false); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 2 * 512, 2 * 512, false); + this.DoUploadFromByteArrayTest(blob, 512, 0, 511, false); + } + + [TestMethod] + [Description("Upload an append blob using a byte array")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadFromByteArrayAPM() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 0, 4 * 512, true); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 0, 2 * 512, true); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 1 * 512, 2 * 512, true); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 2 * 512, 2 * 512, true); + this.DoUploadFromByteArrayTest(blob, 512, 0, 511, true); + } + + [TestMethod] + [Description("Upload an append blob using a byte array")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadBlockFromByteArray() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 0, 4 * 512, false); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 0, 2 * 512, false); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 1 * 512, 2 * 512, false); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 2 * 512, 2 * 512, false); + this.DoUploadFromByteArrayTest(blob, 512, 0, 511, false); + } + + [TestMethod] + [Description("Upload an append blob using a byte array")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadBlockFromByteArrayAPM() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 0, 4 * 512, true); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 0, 2 * 512, true); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 1 * 512, 2 * 512, true); + this.DoUploadFromByteArrayTest(blob, 4 * 512, 2 * 512, 2 * 512, true); + this.DoUploadFromByteArrayTest(blob, 512, 0, 511, true); + } + #if TASK [TestMethod] - [Description("Upload a blob using a byte array")] + [Description("Upload a block blob using a byte array")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -723,7 +992,7 @@ public void CloudBlockBlobUploadFromByteArrayTask() } [TestMethod] - [Description("Upload a blob using a byte array")] + [Description("Upload a page blob using a byte array")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -741,6 +1010,22 @@ public void CloudPageBlobUploadFromByteArrayTask() "Page blobs must be 512-byte aligned"); } + [TestMethod] + [Description("Upload an append blob using a byte array")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadFromByteArrayTask() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoUploadFromByteArrayTestTask(blob, 4 * 512, 0, 4 * 512); + this.DoUploadFromByteArrayTestTask(blob, 4 * 512, 0, 2 * 512); + this.DoUploadFromByteArrayTestTask(blob, 4 * 512, 1 * 512, 2 * 512); + this.DoUploadFromByteArrayTestTask(blob, 4 * 512, 2 * 512, 2 * 512); + this.DoUploadFromByteArrayTestTask(blob, 512, 0, 511); + } + private void DoUploadFromByteArrayTestTask(ICloudBlob blob, int bufferSize, int bufferOffset, int count) { byte[] buffer = GetRandomBuffer(bufferSize); @@ -783,8 +1068,8 @@ private void DoUploadFromByteArrayTest(ICloudBlob blob, int bufferSize, int buff using (AutoResetEvent waitHandle = new AutoResetEvent(false)) { result = blob.BeginUploadFromByteArray(buffer, bufferOffset, count, - ar => waitHandle.Set(), - null); + ar => waitHandle.Set(), + null); waitHandle.WaitOne(); blob.EndUploadFromByteArray(result); @@ -810,7 +1095,7 @@ private void DoUploadFromByteArrayTest(ICloudBlob blob, int bufferSize, int buff } [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a block blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -824,7 +1109,7 @@ public void CloudBlockBlobDownloadToByteArray() } [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a block blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -838,7 +1123,7 @@ public void CloudBlockBlobDownloadToByteArrayAPM() } [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a block blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -853,7 +1138,7 @@ public void CloudBlockBlobDownloadToByteArrayAPMOverload() #if TASK [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a block blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -867,7 +1152,7 @@ public void CloudBlockBlobDownloadToByteArrayTask() } [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a block blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -882,7 +1167,7 @@ public void CloudBlockBlobDownloadToByteArrayOverloadTask() #endif [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -896,7 +1181,7 @@ public void CloudPageBlobDownloadToByteArray() } [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -910,7 +1195,7 @@ public void CloudPageBlobDownloadToByteArrayAPM() } [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -925,7 +1210,7 @@ public void CloudPageBlobDownloadToByteArrayAPMOverload() #if TASK [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -939,7 +1224,7 @@ public void CloudPageBlobDownloadToByteArrayTask() } [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -952,6 +1237,77 @@ public void CloudPageBlobDownloadToByteArrayOverloadTask() this.DoDownloadToByteArrayTestTask(blob, 2 * 512, 4 * 512, 1 * 512, true); } #endif + [TestMethod] + [Description("Single put blob and get blob on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobDownloadToByteArray() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoDownloadToByteArrayTest(blob, 1 * 512, 2 * 512, 0, 0); + this.DoDownloadToByteArrayTest(blob, 1 * 512, 2 * 512, 1 * 512, 0); + this.DoDownloadToByteArrayTest(blob, 2 * 512, 4 * 512, 1 * 512, 0); + } + + [TestMethod] + [Description("Single put blob and get blob on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobDownloadToByteArrayAPM() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoDownloadToByteArrayTest(blob, 1 * 512, 2 * 512, 0, 1); + this.DoDownloadToByteArrayTest(blob, 1 * 512, 2 * 512, 1 * 512, 1); + this.DoDownloadToByteArrayTest(blob, 2 * 512, 4 * 512, 1 * 512, 1); + } + + [TestMethod] + [Description("Single put blob and get blob on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobDownloadToByteArrayAPMOverload() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoDownloadToByteArrayTest(blob, 1 * 512, 2 * 512, 0, 2); + this.DoDownloadToByteArrayTest(blob, 1 * 512, 2 * 512, 1 * 512, 2); + this.DoDownloadToByteArrayTest(blob, 2 * 512, 4 * 512, 1 * 512, 2); + } + +#if TASK + [TestMethod] + [Description("Single put blob and get blob on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobDownloadToByteArrayTask() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoDownloadToByteArrayTestTask(blob, 1 * 512, 2 * 512, 0, false); + this.DoDownloadToByteArrayTestTask(blob, 1 * 512, 2 * 512, 1 * 512, false); + this.DoDownloadToByteArrayTestTask(blob, 2 * 512, 4 * 512, 1 * 512, false); + } + + [TestMethod] + [Description("Single put blob and get blob on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobDownloadToByteArrayOverloadTask() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoDownloadToByteArrayTestTask(blob, 1 * 512, 2 * 512, 0, true); + this.DoDownloadToByteArrayTestTask(blob, 1 * 512, 2 * 512, 1 * 512, true); + this.DoDownloadToByteArrayTestTask(blob, 2 * 512, 4 * 512, 1 * 512, true); + } +#endif /// /// Single put blob and get blob @@ -968,71 +1324,60 @@ private void DoDownloadToByteArrayTest(ICloudBlob blob, int blobSize, int buffer using (MemoryStream originalBlob = new MemoryStream(buffer)) { - if (option == 0) - { - blob.UploadFromStream(originalBlob); - downloadLength = blob.DownloadToByteArray(resultBuffer, bufferOffset); - } - else if (option == 1) - { - using (AutoResetEvent waitHandle = new AutoResetEvent(false)) - { - ICancellableAsyncResult result = blob.BeginUploadFromStream(originalBlob, - ar => waitHandle.Set(), - null); - waitHandle.WaitOne(); - blob.EndUploadFromStream(result); + blob.UploadFromStream(originalBlob); + } - result = blob.BeginDownloadToByteArray(resultBuffer, - bufferOffset, - ar => waitHandle.Set(), - null); - waitHandle.WaitOne(); - downloadLength = blob.EndDownloadToByteArray(result); - } + if (option == 0) + { + downloadLength = blob.DownloadToByteArray(resultBuffer, bufferOffset); + } + else if (option == 1) + { + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + ICancellableAsyncResult result = blob.BeginDownloadToByteArray(resultBuffer, + bufferOffset, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + downloadLength = blob.EndDownloadToByteArray(result); } - else + } + else + { + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) { - using (AutoResetEvent waitHandle = new AutoResetEvent(false)) - { - ICancellableAsyncResult result = blob.BeginUploadFromStream(originalBlob, - ar => waitHandle.Set(), - null); - waitHandle.WaitOne(); - blob.EndUploadFromStream(result); - - OperationContext context = new OperationContext(); - result = blob.BeginDownloadToByteArray(resultBuffer, - bufferOffset, /* offset */ - null, /* accessCondition */ - null, /* options */ - context, /* operationContext */ - ar => waitHandle.Set(), - null); - waitHandle.WaitOne(); - downloadLength = blob.EndDownloadToByteArray(result); - } + OperationContext context = new OperationContext(); + ICancellableAsyncResult result = blob.BeginDownloadToByteArray(resultBuffer, + bufferOffset, /* offset */ + null, /* accessCondition */ + null, /* options */ + context, /* operationContext */ + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + downloadLength = blob.EndDownloadToByteArray(result); } + } - int downloadSize = Math.Min(blobSize, bufferSize - bufferOffset); - Assert.AreEqual(downloadSize, downloadLength); + int downloadSize = Math.Min(blobSize, bufferSize - bufferOffset); + Assert.AreEqual(downloadSize, downloadLength); - for (int i = 0; i < blob.Properties.Length; i++) - { - Assert.AreEqual(buffer[i], resultBuffer[bufferOffset + i]); - } + for (int i = 0; i < blob.Properties.Length; i++) + { + Assert.AreEqual(buffer[i], resultBuffer[bufferOffset + i]); + } - for (int j = 0; j < bufferOffset; j++) - { - Assert.AreEqual(0, resultBuffer2[j]); - } + for (int j = 0; j < bufferOffset; j++) + { + Assert.AreEqual(0, resultBuffer2[j]); + } - if (bufferOffset + blobSize < bufferSize) + if (bufferOffset + blobSize < bufferSize) + { + for (int k = bufferOffset + blobSize; k < bufferSize; k++) { - for (int k = bufferOffset + blobSize; k < bufferSize; k++) - { - Assert.AreEqual(0, resultBuffer2[k]); - } + Assert.AreEqual(0, resultBuffer2[k]); } } } @@ -1054,48 +1399,48 @@ private void DoDownloadToByteArrayTestTask(ICloudBlob blob, int blobSize, int bu using (MemoryStream originalBlob = new MemoryStream(buffer)) { blob.UploadFromStreamAsync(originalBlob).Wait(); - - if (overload) - { - downloadLength = blob.DownloadToByteArrayAsync( - resultBuffer, - bufferOffset, - null, - null, - new OperationContext()) - .Result; - } - else - { - downloadLength = blob.DownloadToByteArrayAsync(resultBuffer, bufferOffset).Result; - } - - int downloadSize = Math.Min(blobSize, bufferSize - bufferOffset); - Assert.AreEqual(downloadSize, downloadLength); + } - for (int i = 0; i < blob.Properties.Length; i++) - { - Assert.AreEqual(buffer[i], resultBuffer[bufferOffset + i]); - } + if (overload) + { + downloadLength = blob.DownloadToByteArrayAsync( + resultBuffer, + bufferOffset, + null, + null, + new OperationContext()) + .Result; + } + else + { + downloadLength = blob.DownloadToByteArrayAsync(resultBuffer, bufferOffset).Result; + } - for (int j = 0; j < bufferOffset; j++) - { - Assert.AreEqual(0, resultBuffer2[j]); - } + int downloadSize = Math.Min(blobSize, bufferSize - bufferOffset); + Assert.AreEqual(downloadSize, downloadLength); + + for (int i = 0; i < blob.Properties.Length; i++) + { + Assert.AreEqual(buffer[i], resultBuffer[bufferOffset + i]); + } - if (bufferOffset + blobSize < bufferSize) + for (int j = 0; j < bufferOffset; j++) + { + Assert.AreEqual(0, resultBuffer2[j]); + } + + if (bufferOffset + blobSize < bufferSize) + { + for (int k = bufferOffset + blobSize; k < bufferSize; k++) { - for (int k = bufferOffset + blobSize; k < bufferSize; k++) - { - Assert.AreEqual(0, resultBuffer2[k]); - } + Assert.AreEqual(0, resultBuffer2[k]); } } } #endif [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a block blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -1120,7 +1465,7 @@ public void CloudBlockBlobDownloadRangeToByteArray() } [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a block blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -1145,7 +1490,7 @@ public void CloudBlockBlobDownloadRangeToByteArrayAPM() } [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a block blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -1171,7 +1516,7 @@ public void CloudBlockBlobDownloadRangeToByteArrayAPMOverload() #if TASK [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a block blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -1196,7 +1541,7 @@ public void CloudBlockBlobDownloadRangeToByteArrayTask() } [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a block blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -1222,7 +1567,7 @@ public void CloudBlockBlobDownloadRangeToByteArrayOverloadTask() #endif [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -1248,7 +1593,7 @@ public void CloudPageBlobDownloadRangeToByteArray() } [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -1273,7 +1618,7 @@ public void CloudPageBlobDownloadRangeToByteArrayAPM() } [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -1299,7 +1644,7 @@ public void CloudPageBlobDownloadRangeToByteArrayAPMOverload() #if TASK [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -1324,7 +1669,7 @@ public void CloudPageBlobDownloadRangeToByteArrayTask() } [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -1348,7 +1693,133 @@ public void CloudPageBlobDownloadRangeToByteArrayOverloadTask() this.DoDownloadRangeToByteArrayTask(blob, 1024, 1024, 512, 0, 512, true); } #endif + [TestMethod] + [Description("Single put blob and get blob on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobDownloadRangeToByteArray() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 0, 1 * 512, 1 * 512, 0); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 1 * 512, null, null, 0); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 1 * 512, 1 * 512, null, 0); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 1 * 512, 0, 1 * 512, 0); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 1 * 512, 0); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 2 * 512, 0); + + // Edge cases + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 1023, 1023, 1, 0); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 0, 1023, 1, 0); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 0, 0, 1, 0); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 0, 512, 1, 0); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 512, 1023, 1, 0); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 512, 0, 512, 0); + + } + + [TestMethod] + [Description("Single put blob and get blob on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobDownloadRangeToByteArrayAPM() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 0, 1 * 512, 1 * 512, 1); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 1 * 512, null, null, 1); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 1 * 512, 1 * 512, null, 1); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 1 * 512, 0, 1 * 512, 1); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 1 * 512, 1); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 2 * 512, 1); + + // Edge cases + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 1023, 1023, 1, 1); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 0, 1023, 1, 1); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 0, 0, 1, 1); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 0, 512, 1, 1); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 512, 1023, 1, 1); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 512, 0, 512, 1); + } + + [TestMethod] + [Description("Single put blob and get blob on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobDownloadRangeToByteArrayAPMOverload() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 0, 1 * 512, 1 * 512, 2); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 1 * 512, null, null, 2); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 1 * 512, 1 * 512, null, 2); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 1 * 512, 0, 1 * 512, 2); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 1 * 512, 2); + this.DoDownloadRangeToByteArray(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 2 * 512, 2); + + // Edge cases + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 1023, 1023, 1, 2); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 0, 1023, 1, 2); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 0, 0, 1, 2); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 0, 512, 1, 2); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 512, 1023, 1, 2); + this.DoDownloadRangeToByteArray(blob, 1024, 1024, 512, 0, 512, 2); + } + +#if TASK + [TestMethod] + [Description("Single put blob and get blob on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobDownloadRangeToByteArrayTask() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoDownloadRangeToByteArrayTask(blob, 2 * 512, 4 * 512, 0, 1 * 512, 1 * 512, false); + this.DoDownloadRangeToByteArrayTask(blob, 2 * 512, 4 * 512, 1 * 512, null, null, false); + this.DoDownloadRangeToByteArrayTask(blob, 2 * 512, 4 * 512, 1 * 512, 1 * 512, null, false); + this.DoDownloadRangeToByteArrayTask(blob, 2 * 512, 4 * 512, 1 * 512, 0, 1 * 512, false); + this.DoDownloadRangeToByteArrayTask(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 1 * 512, false); + this.DoDownloadRangeToByteArrayTask(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 2 * 512, false); + + // Edge cases + this.DoDownloadRangeToByteArrayTask(blob, 1024, 1024, 1023, 1023, 1, false); + this.DoDownloadRangeToByteArrayTask(blob, 1024, 1024, 0, 1023, 1, false); + this.DoDownloadRangeToByteArrayTask(blob, 1024, 1024, 0, 0, 1, false); + this.DoDownloadRangeToByteArrayTask(blob, 1024, 1024, 0, 512, 1, false); + this.DoDownloadRangeToByteArrayTask(blob, 1024, 1024, 512, 1023, 1, false); + this.DoDownloadRangeToByteArrayTask(blob, 1024, 1024, 512, 0, 512, false); + } + + [TestMethod] + [Description("Single put blob and get blob on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobDownloadRangeToByteArrayOverloadTask() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + this.DoDownloadRangeToByteArrayTask(blob, 2 * 512, 4 * 512, 0, 1 * 512, 1 * 512, true); + this.DoDownloadRangeToByteArrayTask(blob, 2 * 512, 4 * 512, 1 * 512, null, null, true); + this.DoDownloadRangeToByteArrayTask(blob, 2 * 512, 4 * 512, 1 * 512, 1 * 512, null, true); + this.DoDownloadRangeToByteArrayTask(blob, 2 * 512, 4 * 512, 1 * 512, 0, 1 * 512, true); + this.DoDownloadRangeToByteArrayTask(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 1 * 512, true); + this.DoDownloadRangeToByteArrayTask(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 2 * 512, true); + // Edge cases + this.DoDownloadRangeToByteArrayTask(blob, 1024, 1024, 1023, 1023, 1, true); + this.DoDownloadRangeToByteArrayTask(blob, 1024, 1024, 0, 1023, 1, true); + this.DoDownloadRangeToByteArrayTask(blob, 1024, 1024, 0, 0, 1, true); + this.DoDownloadRangeToByteArrayTask(blob, 1024, 1024, 0, 512, 1, true); + this.DoDownloadRangeToByteArrayTask(blob, 1024, 1024, 512, 1023, 1, true); + this.DoDownloadRangeToByteArrayTask(blob, 1024, 1024, 512, 0, 512, true); + } +#endif /// /// Single put blob and get blob /// @@ -1367,78 +1838,67 @@ private void DoDownloadRangeToByteArray(ICloudBlob blob, int blobSize, int buffe using (MemoryStream originalBlob = new MemoryStream(buffer)) { - if (option == 0) - { - blob.UploadFromStream(originalBlob); - downloadLength = blob.DownloadRangeToByteArray(resultBuffer, bufferOffset, blobOffset, length); - } - else if (option == 1) - { - using (AutoResetEvent waitHandle = new AutoResetEvent(false)) - { - ICancellableAsyncResult result = blob.BeginUploadFromStream(originalBlob, - ar => waitHandle.Set(), - null); - waitHandle.WaitOne(); - blob.EndUploadFromStream(result); + blob.UploadFromStream(originalBlob); + } - result = blob.BeginDownloadRangeToByteArray(resultBuffer, - bufferOffset, - blobOffset, - length, - ar => waitHandle.Set(), - null); - waitHandle.WaitOne(); - downloadLength = blob.EndDownloadRangeToByteArray(result); - } - } - else + if (option == 0) + { + downloadLength = blob.DownloadRangeToByteArray(resultBuffer, bufferOffset, blobOffset, length); + } + else if (option == 1) + { + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) { - using (AutoResetEvent waitHandle = new AutoResetEvent(false)) - { - ICancellableAsyncResult result = blob.BeginUploadFromStream(originalBlob, - ar => waitHandle.Set(), - null); - waitHandle.WaitOne(); - blob.EndUploadFromStream(result); - - OperationContext context = new OperationContext(); - result = blob.BeginDownloadRangeToByteArray(resultBuffer, - bufferOffset, - blobOffset, - length, - null, - null, - context, - ar => waitHandle.Set(), - null); - waitHandle.WaitOne(); - downloadLength = blob.EndDownloadRangeToByteArray(result); - } + ICancellableAsyncResult result = blob.BeginDownloadRangeToByteArray(resultBuffer, + bufferOffset, + blobOffset, + length, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + downloadLength = blob.EndDownloadRangeToByteArray(result); } - - int downloadSize = Math.Min(blobSize - (int)(blobOffset.HasValue ? blobOffset.Value : 0), bufferSize - bufferOffset); - if (length.HasValue && (length.Value < downloadSize)) + } + else + { + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) { - downloadSize = (int)length.Value; + OperationContext context = new OperationContext(); + ICancellableAsyncResult result = blob.BeginDownloadRangeToByteArray(resultBuffer, + bufferOffset, + blobOffset, + length, + null, + null, + context, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + downloadLength = blob.EndDownloadRangeToByteArray(result); } + } - Assert.AreEqual(downloadSize, downloadLength); + int downloadSize = Math.Min(blobSize - (int)(blobOffset.HasValue ? blobOffset.Value : 0), bufferSize - bufferOffset); + if (length.HasValue && (length.Value < downloadSize)) + { + downloadSize = (int)length.Value; + } - for (int i = 0; i < bufferOffset; i++) - { - Assert.AreEqual(0, resultBuffer[i]); - } + Assert.AreEqual(downloadSize, downloadLength); - for (int j = 0; j < downloadLength; j++) - { - Assert.AreEqual(buffer[(blobOffset.HasValue ? blobOffset.Value : 0) + j], resultBuffer[bufferOffset + j]); - } + for (int i = 0; i < bufferOffset; i++) + { + Assert.AreEqual(0, resultBuffer[i]); + } - for (int k = bufferOffset + downloadLength; k < bufferSize; k++) - { - Assert.AreEqual(0, resultBuffer[k]); - } + for (int j = 0; j < downloadLength; j++) + { + Assert.AreEqual(buffer[(blobOffset.HasValue ? blobOffset.Value : 0) + j], resultBuffer[bufferOffset + j]); + } + + for (int k = bufferOffset + downloadLength; k < bufferSize; k++) + { + Assert.AreEqual(0, resultBuffer[k]); } } @@ -1461,47 +1921,47 @@ private void DoDownloadRangeToByteArrayTask(ICloudBlob blob, int blobSize, int b using (MemoryStream originalBlob = new MemoryStream(buffer)) { - if (overload) - { - blob.UploadFromStream(originalBlob); - downloadLength = blob.DownloadRangeToByteArrayAsync( - resultBuffer, bufferOffset, blobOffset, length, null, null, new OperationContext()).Result; - } - else - { - blob.UploadFromStream(originalBlob); - downloadLength = blob.DownloadRangeToByteArrayAsync(resultBuffer, bufferOffset, blobOffset, length).Result; - } - - int downloadSize = Math.Min(blobSize - (int)(blobOffset.HasValue ? blobOffset.Value : 0), bufferSize - bufferOffset); - if (length.HasValue && (length.Value < downloadSize)) - { - downloadSize = (int)length.Value; - } + blob.UploadFromStreamAsync(originalBlob).Wait(); + } - Assert.AreEqual(downloadSize, downloadLength); + if (overload) + { + downloadLength = blob.DownloadRangeToByteArrayAsync( + resultBuffer, bufferOffset, blobOffset, length, null, null, new OperationContext()).Result; + } + else + { + downloadLength = blob.DownloadRangeToByteArrayAsync(resultBuffer, bufferOffset, blobOffset, length).Result; + } - for (int i = 0; i < bufferOffset; i++) - { - Assert.AreEqual(0, resultBuffer[i]); - } + int downloadSize = Math.Min(blobSize - (int)(blobOffset.HasValue ? blobOffset.Value : 0), bufferSize - bufferOffset); + if (length.HasValue && (length.Value < downloadSize)) + { + downloadSize = (int)length.Value; + } - for (int j = 0; j < downloadLength; j++) - { - Assert.AreEqual(buffer[(blobOffset.HasValue ? blobOffset.Value : 0) + j], resultBuffer[bufferOffset + j]); - } + Assert.AreEqual(downloadSize, downloadLength); - for (int k = bufferOffset + downloadLength; k < bufferSize; k++) - { - Assert.AreEqual(0, resultBuffer[k]); - } + for (int i = 0; i < bufferOffset; i++) + { + Assert.AreEqual(0, resultBuffer[i]); + } + + for (int j = 0; j < downloadLength; j++) + { + Assert.AreEqual(buffer[(blobOffset.HasValue ? blobOffset.Value : 0) + j], resultBuffer[bufferOffset + j]); + } + + for (int k = bufferOffset + downloadLength; k < bufferSize; k++) + { + Assert.AreEqual(0, resultBuffer[k]); } } #endif #region Negative tests [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a block blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] @@ -1513,7 +1973,7 @@ public void CloudBlockBlobDownloadRangeToByteArrayNegativeTests() } [TestMethod] - [Description("Single put blob and get blob")] + [Description("Single put blob and get blob on a page blob")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] diff --git a/Test/ClassLibraryCommon/Blob/BlobWriteStreamTest.cs b/Test/ClassLibraryCommon/Blob/BlobWriteStreamTest.cs index 4ceae6f93..977df00cc 100644 --- a/Test/ClassLibraryCommon/Blob/BlobWriteStreamTest.cs +++ b/Test/ClassLibraryCommon/Blob/BlobWriteStreamTest.cs @@ -16,6 +16,7 @@ // ----------------------------------------------------------------------------------------- using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.WindowsAzure.Storage.Core; using System; using System.Collections.Generic; using System.IO; @@ -67,7 +68,7 @@ public void BlobWriteStreamOpenAndClose() { } - CloudBlockBlob blockBlob2 = container.GetBlockBlobReference("blob1"); + CloudBlockBlob blockBlob2 = container.GetBlockBlobReference(blockBlob.Name); blockBlob2.FetchAttributes(); Assert.AreEqual(0, blockBlob2.Properties.Length); Assert.AreEqual(BlobType.BlockBlob, blockBlob2.Properties.BlobType); @@ -84,10 +85,20 @@ public void BlobWriteStreamOpenAndClose() { } - CloudPageBlob pageBlob2 = container.GetPageBlobReference("blob2"); + CloudPageBlob pageBlob2 = container.GetPageBlobReference(pageBlob.Name); pageBlob2.FetchAttributes(); Assert.AreEqual(1024, pageBlob2.Properties.Length); Assert.AreEqual(BlobType.PageBlob, pageBlob2.Properties.BlobType); + + CloudAppendBlob appendBlob = container.GetAppendBlobReference("blob3"); + using (Stream blobStream = appendBlob.OpenWrite(true)) + { + } + + CloudAppendBlob appendBlob2 = container.GetAppendBlobReference(appendBlob.Name); + appendBlob2.FetchAttributes(); + Assert.AreEqual(0, appendBlob2.Properties.Length); + Assert.AreEqual(BlobType.AppendBlob, appendBlob2.Properties.BlobType); } finally { @@ -1663,5 +1674,867 @@ public void PageBlobWriteStreamFlushTestAPM() container.DeleteIfExists(); } } + + [TestMethod] + [Description("Upload an append blob using blob stream and verify contents")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobWriteStreamBasicTest() + { + byte[] buffer = GetRandomBuffer(3 * 1024 * 1024); + + MD5 hasher = MD5.Create(); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + using (MemoryStream wholeBlob = new MemoryStream()) + { + BlobRequestOptions options = new BlobRequestOptions() + { + StoreBlobContentMD5 = true, + }; + + using (Stream blobStream = blob.OpenWrite(true, null, options)) + { + for (int i = 0; i < 3; i++) + { + blobStream.Write(buffer, 0, buffer.Length); + wholeBlob.Write(buffer, 0, buffer.Length); + Assert.AreEqual(wholeBlob.Position, blobStream.Position); + } + } + + wholeBlob.Seek(0, SeekOrigin.Begin); + string md5 = Convert.ToBase64String(hasher.ComputeHash(wholeBlob)); + blob.FetchAttributes(); + Assert.AreEqual(md5, blob.Properties.ContentMD5); + + using (MemoryStream downloadedBlob = new MemoryStream()) + { + blob.DownloadToStream(downloadedBlob); + TestHelper.AssertStreamsAreEqual(wholeBlob, downloadedBlob); + } + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Upload an append blob using blob stream and verify contents")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobWriteStreamOneByteTest() + { + byte buffer = 127; + + MD5 hasher = MD5.Create(); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamWriteSizeInBytes = 16 * 1024; + using (MemoryStream wholeBlob = new MemoryStream()) + { + BlobRequestOptions options = new BlobRequestOptions() + { + StoreBlobContentMD5 = true, + }; + using (Stream blobStream = blob.OpenWrite(true, null, options, null)) + { + for (int i = 0; i < 1 * 1024 * 1024; i++) + { + blobStream.WriteByte(buffer); + wholeBlob.WriteByte(buffer); + Assert.AreEqual(wholeBlob.Position, blobStream.Position); + } + } + + wholeBlob.Seek(0, SeekOrigin.Begin); + string md5 = Convert.ToBase64String(hasher.ComputeHash(wholeBlob)); + blob.FetchAttributes(); + Assert.AreEqual(md5, blob.Properties.ContentMD5); + + using (MemoryStream downloadedBlob = new MemoryStream()) + { + blob.DownloadToStream(downloadedBlob); + TestHelper.AssertStreamsAreEqual(wholeBlob, downloadedBlob); + } + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Seek in a blob write stream")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobWriteStreamSeekTest() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + using (Stream blobStream = blob.OpenWrite(true)) + { + TestHelper.ExpectedException( + () => blobStream.Seek(1, SeekOrigin.Begin), + "Append blob write stream should not be seekable"); + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Create a blob using blob stream by specifying an access condition")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobWriteStreamOpenWithAccessCondition() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.Create(); + + try + { + CloudAppendBlob existingBlob = container.GetAppendBlobReference("blob"); + existingBlob.CreateOrReplace(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob2"); + AccessCondition accessCondition = AccessCondition.GenerateIfMatchCondition(existingBlob.Properties.ETag); + TestHelper.ExpectedException( + () => blob.OpenWrite(true, accessCondition), + "OpenWrite with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + blob = container.GetAppendBlobReference("blob3"); + accessCondition = AccessCondition.GenerateIfNoneMatchCondition(existingBlob.Properties.ETag); + Stream blobStream = blob.OpenWrite(true, accessCondition); + blobStream.Dispose(); + + blob = container.GetAppendBlobReference("blob4"); + accessCondition = AccessCondition.GenerateIfNoneMatchCondition("*"); + blobStream = blob.OpenWrite(true, accessCondition); + blobStream.Dispose(); + + blob = container.GetAppendBlobReference("blob5"); + accessCondition = AccessCondition.GenerateIfModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(1)); + blobStream = blob.OpenWrite(true, accessCondition); + blobStream.Dispose(); + + blob = container.GetAppendBlobReference("blob6"); + accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(-1)); + blobStream = blob.OpenWrite(true, accessCondition); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfMatchCondition(existingBlob.Properties.ETag); + blobStream = existingBlob.OpenWrite(true, accessCondition); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfMatchCondition(blob.Properties.ETag); + TestHelper.ExpectedException( + () => existingBlob.OpenWrite(true, accessCondition), + "OpenWrite with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + accessCondition = AccessCondition.GenerateIfNoneMatchCondition(blob.Properties.ETag); + blobStream = existingBlob.OpenWrite(true, accessCondition); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfNoneMatchCondition(existingBlob.Properties.ETag); + TestHelper.ExpectedException( + () => existingBlob.OpenWrite(true, accessCondition), + "OpenWrite with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + accessCondition = AccessCondition.GenerateIfNoneMatchCondition("*"); + TestHelper.ExpectedException( + () => existingBlob.OpenWrite(true, accessCondition), + "OpenWrite with a non-met condition should fail", + HttpStatusCode.Conflict); + + accessCondition = AccessCondition.GenerateIfModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(-1)); + blobStream = existingBlob.OpenWrite(true, accessCondition); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(1)); + TestHelper.ExpectedException( + () => existingBlob.OpenWrite(true, accessCondition), + "OpenWrite with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(1)); + blobStream = existingBlob.OpenWrite(true, accessCondition); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(-1)); + TestHelper.ExpectedException( + () => existingBlob.OpenWrite(true, accessCondition), + "OpenWrite with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + } + finally + { + container.Delete(); + } + } + + [TestMethod] + [Description("Create a blob using blob stream by specifying an access condition")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobWriteStreamOpenAPMWithAccessCondition() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.Create(); + + try + { + CloudAppendBlob existingBlob = container.GetAppendBlobReference("blob"); + existingBlob.CreateOrReplace(); + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + CloudAppendBlob blob = container.GetAppendBlobReference("blob2"); + AccessCondition accessCondition = AccessCondition.GenerateIfMatchCondition(existingBlob.Properties.ETag); + IAsyncResult result = blob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + TestHelper.ExpectedException( + () => blob.EndOpenWrite(result), + "OpenWrite with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + blob = container.GetAppendBlobReference("blob3"); + accessCondition = AccessCondition.GenerateIfNoneMatchCondition(existingBlob.Properties.ETag); + result = blob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + Stream blobStream = blob.EndOpenWrite(result); + blobStream.Dispose(); + + blob = container.GetAppendBlobReference("blob4"); + accessCondition = AccessCondition.GenerateIfNoneMatchCondition("*"); + result = blob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blobStream = blob.EndOpenWrite(result); + blobStream.Dispose(); + + blob = container.GetAppendBlobReference("blob5"); + accessCondition = AccessCondition.GenerateIfModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(1)); + result = blob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blobStream = blob.EndOpenWrite(result); + blobStream.Dispose(); + + blob = container.GetAppendBlobReference("blob6"); + accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(-1)); + result = blob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blobStream = blob.EndOpenWrite(result); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfMatchCondition(existingBlob.Properties.ETag); + result = existingBlob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blobStream = existingBlob.EndOpenWrite(result); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfMatchCondition(blob.Properties.ETag); + result = existingBlob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + TestHelper.ExpectedException( + () => existingBlob.EndOpenWrite(result), + "OpenWrite with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + accessCondition = AccessCondition.GenerateIfNoneMatchCondition(blob.Properties.ETag); + result = existingBlob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blobStream = existingBlob.EndOpenWrite(result); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfNoneMatchCondition(existingBlob.Properties.ETag); + result = existingBlob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + TestHelper.ExpectedException( + () => existingBlob.EndOpenWrite(result), + "OpenWrite with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + accessCondition = AccessCondition.GenerateIfNoneMatchCondition("*"); + result = existingBlob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + TestHelper.ExpectedException( + () => existingBlob.EndOpenWrite(result), + "BlobWriteStream.Dispose with a non-met condition should fail", + HttpStatusCode.Conflict); + + accessCondition = AccessCondition.GenerateIfModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(-1)); + result = existingBlob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blobStream = existingBlob.EndOpenWrite(result); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(1)); + result = existingBlob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + TestHelper.ExpectedException( + () => existingBlob.EndOpenWrite(result), + "OpenWrite with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(1)); + result = existingBlob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blobStream = existingBlob.EndOpenWrite(result); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(-1)); + result = existingBlob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + TestHelper.ExpectedException( + () => existingBlob.EndOpenWrite(result), + "OpenWrite with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + } + } + finally + { + container.Delete(); + } + } + +#if TASK + [TestMethod] + [Description("Create a blob using blob stream by specifying an access condition")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobWriteStreamOpenWithAccessConditionTask() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.CreateAsync().Wait(); + + try + { + CloudAppendBlob existingBlob = container.GetAppendBlobReference("blob"); + existingBlob.CreateOrReplaceAsync().Wait(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob2"); + AccessCondition accessCondition = AccessCondition.GenerateIfMatchCondition(existingBlob.Properties.ETag); + TestHelper.ExpectedExceptionTask( + blob.OpenWriteAsync(true, accessCondition, null, null), + "OpenWrite with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + blob = container.GetAppendBlobReference("blob3"); + accessCondition = AccessCondition.GenerateIfNoneMatchCondition(existingBlob.Properties.ETag); + Stream blobStream = blob.OpenWriteAsync(true, accessCondition, null, null).Result; + blobStream.Dispose(); + + blob = container.GetAppendBlobReference("blob4"); + accessCondition = AccessCondition.GenerateIfNoneMatchCondition("*"); + blobStream = blob.OpenWriteAsync(true, accessCondition, null, null).Result; + blobStream.Dispose(); + } + finally + { + container.DeleteAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Test the effects of blob stream's flush functionality")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobWriteStreamFlushTest() + { + byte[] buffer = GetRandomBuffer(512 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamWriteSizeInBytes = 1 * 1024 * 1024; + using (MemoryStream wholeBlob = new MemoryStream()) + { + BlobRequestOptions options = new BlobRequestOptions() { StoreBlobContentMD5 = true }; + OperationContext opContext = new OperationContext(); + using (CloudBlobStream blobStream = blob.OpenWrite(true, null, options, opContext)) + { + for (int i = 0; i < 3; i++) + { + blobStream.Write(buffer, 0, buffer.Length); + wholeBlob.Write(buffer, 0, buffer.Length); + } + + Assert.AreEqual(2, opContext.RequestResults.Count); + + blobStream.Flush(); + + Assert.AreEqual(3, opContext.RequestResults.Count); + + blobStream.Flush(); + + Assert.AreEqual(3, opContext.RequestResults.Count); + + blobStream.Write(buffer, 0, buffer.Length); + wholeBlob.Write(buffer, 0, buffer.Length); + + Assert.AreEqual(3, opContext.RequestResults.Count); + + blobStream.Commit(); + + Assert.AreEqual(5, opContext.RequestResults.Count); + } + + Assert.AreEqual(5, opContext.RequestResults.Count); + + using (MemoryStream downloadedBlob = new MemoryStream()) + { + blob.DownloadToStream(downloadedBlob); + TestHelper.AssertStreamsAreEqual(wholeBlob, downloadedBlob); + } + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Test the effects of blob stream's flush functionality")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobWriteStreamFlushTestAPM() + { + byte[] buffer = GetRandomBuffer(512 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamWriteSizeInBytes = 1 * 1024 * 1024; + using (MemoryStream wholeBlob = new MemoryStream()) + { + BlobRequestOptions options = new BlobRequestOptions() { StoreBlobContentMD5 = true }; + OperationContext opContext = new OperationContext(); + using (CloudBlobStream blobStream = blob.OpenWrite(true, null, options, opContext)) + { + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result; + for (int i = 0; i < 3; i++) + { + result = blobStream.BeginWrite( + buffer, + 0, + buffer.Length, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blobStream.EndWrite(result); + wholeBlob.Write(buffer, 0, buffer.Length); + } + + Assert.AreEqual(2, opContext.RequestResults.Count); + + ICancellableAsyncResult cancellableResult = blobStream.BeginFlush( + ar => waitHandle.Set(), + null); + Assert.IsFalse(cancellableResult.IsCompleted); + cancellableResult.Cancel(); + waitHandle.WaitOne(); + blobStream.EndFlush(cancellableResult); + + result = blobStream.BeginFlush( + ar => waitHandle.Set(), + null); + Assert.IsFalse(result.IsCompleted); + TestHelper.ExpectedException( + () => blobStream.BeginFlush(null, null), + null); + waitHandle.WaitOne(); + TestHelper.ExpectedException( + () => blobStream.BeginFlush(null, null), + null); + blobStream.EndFlush(result); + Assert.IsFalse(result.CompletedSynchronously); + + Assert.AreEqual(3, opContext.RequestResults.Count); + + result = blobStream.BeginFlush( + ar => waitHandle.Set(), + null); + Assert.IsTrue(result.CompletedSynchronously); + waitHandle.WaitOne(); + blobStream.EndFlush(result); + + Assert.AreEqual(3, opContext.RequestResults.Count); + + result = blobStream.BeginWrite( + buffer, + 0, + buffer.Length, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blobStream.EndWrite(result); + wholeBlob.Write(buffer, 0, buffer.Length); + + Assert.AreEqual(3, opContext.RequestResults.Count); + + result = blobStream.BeginCommit( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blobStream.EndCommit(result); + + Assert.AreEqual(5, opContext.RequestResults.Count); + } + } + + Assert.AreEqual(5, opContext.RequestResults.Count); + + using (MemoryStream downloadedBlob = new MemoryStream()) + { + blob.DownloadToStream(downloadedBlob); + TestHelper.AssertStreamsAreEqual(wholeBlob, downloadedBlob); + } + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Validate that we use user's access condition for the first attempt write.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobWriteStreamAppendOffsetTest() + { + byte[] buffer = GetRandomBuffer(3 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + + using (MemoryStream wholeBlob = new MemoryStream()) + { + blob.AppendBlock(new MemoryStream(buffer)); + wholeBlob.Write(buffer, 0, buffer.Length); + + AccessCondition condition = new AccessCondition() + { + IfMatchETag = blob.Properties.ETag + }; + + OperationContext context = new OperationContext(); + int count = 0; + context.SendingRequest += (sender, e) => + { + if (e.Request.Headers["If-Match"] != null) + { + count++; + } + else + { + Assert.AreEqual(buffer.Length, int.Parse(e.Request.Headers["x-ms-blob-condition-appendpos"])); + } + }; + + // Even though the condition does not have an append position set, the client lib will internally + // make a FetchAttributes call to set the correct append position for the first operation. + using (Stream blobStream = blob.OpenWrite(false, condition, null, context)) + { + blobStream.Write(buffer, 0, buffer.Length); + wholeBlob.Write(buffer, 0, buffer.Length); + } + + Assert.AreEqual(1, count); + + blob.FetchAttributes(); + + // The length is 6MB since we uploaded 3 MB using AppendBlobFromStream and then 3MB using Write. + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + wholeBlob.Seek(0, SeekOrigin.Begin); + + using (MemoryStream downloadedBlob = new MemoryStream()) + { + blob.DownloadToStream(downloadedBlob); + TestHelper.AssertStreamsAreEqual(wholeBlob, downloadedBlob); + } + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Validate that we use user's access condition for the first attempt write.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobWriteStreamAppendOffsetTestAPM() + { + byte[] buffer = GetRandomBuffer(3 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + + using (MemoryStream wholeBlob = new MemoryStream()) + { + blob.AppendBlock(new MemoryStream(buffer)); + wholeBlob.Write(buffer, 0, buffer.Length); + + AccessCondition condition = new AccessCondition() + { + IfMatchETag = blob.Properties.ETag + }; + + OperationContext context = new OperationContext(); + int count = 0; + context.SendingRequest += (sender, e) => + { + if (e.Request.Headers["If-Match"] != null) + { + count++; + } + else + { + Assert.AreEqual(buffer.Length, int.Parse(e.Request.Headers["x-ms-blob-condition-appendpos"])); + } + }; + + // Even though the condition does not have an append position set, the client lib will internally + // make a FetchAttributes call to set the correct append position for the first operation. + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = blob.BeginOpenWrite(false, condition, null, context, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + using (Stream blobStream = blob.EndOpenWrite(result)) + { + result = blobStream.BeginWrite( + buffer, + 0, + buffer.Length, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blobStream.EndWrite(result); + wholeBlob.Write(buffer, 0, buffer.Length); + } + } + + Assert.AreEqual(1, count); + + blob.FetchAttributes(); + + // The length is 6MB since we uploaded 3 MB using AppendBlobFromStream and then 3MB using Write. + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + wholeBlob.Seek(0, SeekOrigin.Begin); + + using (MemoryStream downloadedBlob = new MemoryStream()) + { + blob.DownloadToStream(downloadedBlob); + TestHelper.AssertStreamsAreEqual(wholeBlob, downloadedBlob); + } + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Upload an append blob using blob stream and verify that max conditions is passed through")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobWriteStreamMaxSizeConditionTest() + { + byte[] buffer = GetRandomBuffer(16 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamWriteSizeInBytes = 16 * 1024; + using (MemoryStream wholeBlob = new MemoryStream()) + { + AccessCondition accessCondition = new AccessCondition() { IfMaxSizeLessThanOrEqual = 34 * 1024 }; + try + { + using (Stream blobStream = blob.OpenWrite(true, accessCondition, null)) + { + for (int i = 0; i < 3; i++) + { + blobStream.Write(buffer, 0, buffer.Length); + wholeBlob.Write(buffer, 0, buffer.Length); + Assert.AreEqual(wholeBlob.Position, blobStream.Position); + } + } + + Assert.Fail("No exception received while expecting condition failure"); + } + catch (IOException ex) + { + Assert.AreEqual(SR.InvalidBlockSize, ex.Message); + } + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Upload an append blob using blob stream and verify that max conditions is passed through")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void AppendBlobWriteStreamMaxSizeConditionTestAPM() + { + byte[] buffer = GetRandomBuffer(16 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamWriteSizeInBytes = 16 * 1024; + using (MemoryStream wholeBlob = new MemoryStream()) + { + AccessCondition accessCondition = new AccessCondition() { IfMaxSizeLessThanOrEqual = 34 * 1024 }; + try + { + // Even though the condition does not have an append position set, the client lib will internally + // make a FetchAttributes call to set the correct append position for the first operation. + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = blob.BeginOpenWrite(true, accessCondition, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + using (Stream blobStream = blob.EndOpenWrite(result)) + { + for (int i = 0; i < 3; i++) + { + result = blobStream.BeginWrite( + buffer, + 0, + buffer.Length, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blobStream.EndWrite(result); + wholeBlob.Write(buffer, 0, buffer.Length); + Assert.AreEqual(wholeBlob.Position, blobStream.Position); + } + } + + Assert.Fail("No exception received while expecting condition failure"); + } + } + catch (IOException ex) + { + Assert.AreEqual(SR.InvalidBlockSize, ex.Message); + } + } + } + finally + { + container.DeleteIfExists(); + } + } } } diff --git a/Test/ClassLibraryCommon/Blob/CloudAppendBlobTest.cs b/Test/ClassLibraryCommon/Blob/CloudAppendBlobTest.cs new file mode 100644 index 000000000..152755c77 --- /dev/null +++ b/Test/ClassLibraryCommon/Blob/CloudAppendBlobTest.cs @@ -0,0 +1,3091 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.WindowsAzure.Storage.Auth; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Threading; + +namespace Microsoft.WindowsAzure.Storage.Blob +{ + [TestClass] + public class CloudAppendBlobTest : BlobTestBase + { + // + // Use TestInitialize to run code before running each test + [TestInitialize()] + public void MyTestInitialize() + { + if (TestBase.BlobBufferManager != null) + { + TestBase.BlobBufferManager.OutstandingBufferCount = 0; + } + } + // + // Use TestCleanup to run code after each test has run + [TestCleanup()] + public void MyTestCleanup() + { + if (TestBase.BlobBufferManager != null) + { + Assert.AreEqual(0, TestBase.BlobBufferManager.OutstandingBufferCount); + } + } + + [TestMethod] + [Description("Create an append blob and then delete it")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobCreateAndDelete() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + Assert.IsTrue(blob.Exists()); + blob.Delete(); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Create an append blob and then delete it")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobCreateAndDeleteAPM() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + IAsyncResult result; + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + result = blob.BeginCreateOrReplace(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndCreateOrReplace(result); + + result = blob.BeginExists(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + Assert.IsTrue(blob.EndExists(result)); + + result = blob.BeginDelete(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndDelete(result); + } + } + finally + { + container.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Create a zero-length append blob and then delete it")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobCreateAndDeleteTask() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.CreateAsync().Wait(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplaceAsync().Wait(); + Assert.IsTrue(blob.ExistsAsync().Result); + blob.DeleteAsync().Wait(); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Get an append blob reference using its constructor")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobConstructor() + { + CloudBlobContainer container = GetRandomContainerReference(); + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + CloudAppendBlob blob2 = new CloudAppendBlob(blob.StorageUri, null, null); + Assert.AreEqual(blob.Name, blob2.Name); + Assert.AreEqual(blob.StorageUri, blob2.StorageUri); + Assert.AreEqual(blob.Container.StorageUri, blob2.Container.StorageUri); + Assert.AreEqual(blob.ServiceClient.StorageUri, blob2.ServiceClient.StorageUri); + Assert.AreEqual(blob.BlobType, blob2.BlobType); + } + + [TestMethod] + [Description("Try to delete a non-existing append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobDeleteIfExists() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + Assert.IsFalse(blob.DeleteIfExists()); + blob.CreateOrReplace(); + Assert.IsTrue(blob.DeleteIfExists()); + Assert.IsFalse(blob.DeleteIfExists()); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Try to delete a non-existing append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobDeleteIfExistsAPM() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + IAsyncResult result = blob.BeginDeleteIfExists( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + Assert.IsFalse(blob.EndDeleteIfExists(result)); + result = blob.BeginCreateOrReplace( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob.EndCreateOrReplace(result); + result = blob.BeginDeleteIfExists( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + Assert.IsTrue(blob.EndDeleteIfExists(result)); + result = blob.BeginDeleteIfExists( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + Assert.IsFalse(blob.EndDeleteIfExists(result)); + } + } + finally + { + container.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Try to delete a non-existing append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobDeleteIfExistsTask() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.CreateAsync().Wait(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + Assert.IsFalse(blob.DeleteIfExistsAsync().Result); + blob.CreateOrReplaceAsync().Wait(); + Assert.IsTrue(blob.DeleteIfExistsAsync().Result); + Assert.IsFalse(blob.DeleteIfExistsAsync().Result); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Check a blob's existence")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobExists() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.Create(); + + try + { + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + + Assert.IsFalse(blob2.Exists()); + + blob.CreateOrReplace(); + + Assert.IsTrue(blob2.Exists()); + Assert.AreEqual(0, blob2.Properties.Length); + + blob.Delete(); + + Assert.IsFalse(blob2.Exists()); + } + finally + { + container.Delete(); + } + } + + [TestMethod] + [Description("Check a blob's existence")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobExistsAPM() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.Create(); + + try + { + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = blob2.BeginExists( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + Assert.IsFalse(blob2.EndExists(result)); + + blob.CreateOrReplace(); + + result = blob2.BeginExists( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + Assert.IsTrue(blob2.EndExists(result)); + Assert.AreEqual(0, blob2.Properties.Length); + + blob.Delete(); + + result = blob2.BeginExists( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + Assert.IsFalse(blob2.EndExists(result)); + } + } + finally + { + container.Delete(); + } + } + +#if TASK + [TestMethod] + [Description("Check a blob's existence")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobExistsTask() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.CreateAsync().Wait(); + + try + { + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + + Assert.IsFalse(blob2.ExistsAsync().Result); + + blob.CreateOrReplaceAsync().Wait(); + + Assert.IsTrue(blob2.ExistsAsync().Result); + Assert.AreEqual(0, blob2.Properties.Length); + + blob.DeleteAsync().Wait(); + + Assert.IsFalse(blob2.ExistsAsync().Result); + } + finally + { + container.DeleteAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Verify the attributes of a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobFetchAttributes() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + Assert.AreEqual(0, blob.Properties.Length); + Assert.IsNotNull(blob.Properties.ETag); + Assert.IsTrue(blob.Properties.LastModified > DateTimeOffset.UtcNow.AddMinutes(-5)); + Assert.IsNull(blob.Properties.CacheControl); + Assert.IsNull(blob.Properties.ContentDisposition); + Assert.IsNull(blob.Properties.ContentEncoding); + Assert.IsNull(blob.Properties.ContentLanguage); + Assert.IsNull(blob.Properties.ContentType); + Assert.IsNull(blob.Properties.ContentMD5); + Assert.AreEqual(LeaseStatus.Unspecified, blob.Properties.LeaseStatus); + Assert.AreEqual(BlobType.AppendBlob, blob.Properties.BlobType); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + blob2.FetchAttributes(); + Assert.AreEqual(0, blob2.Properties.Length); + Assert.AreEqual(blob.Properties.ETag, blob2.Properties.ETag); + Assert.AreEqual(blob.Properties.LastModified, blob2.Properties.LastModified); + Assert.IsNull(blob2.Properties.CacheControl); + Assert.IsNull(blob2.Properties.ContentDisposition); + Assert.IsNull(blob2.Properties.ContentEncoding); + Assert.IsNull(blob2.Properties.ContentLanguage); + Assert.AreEqual("application/octet-stream", blob2.Properties.ContentType); + Assert.IsNull(blob2.Properties.ContentMD5); + Assert.AreEqual(LeaseStatus.Unlocked, blob2.Properties.LeaseStatus); + Assert.AreEqual(BlobType.AppendBlob, blob2.Properties.BlobType); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Verify the attributes of a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobFetchAttributesAPM() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + IAsyncResult result = blob.BeginCreateOrReplace( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob.EndCreateOrReplace(result); + Assert.AreEqual(0, blob.Properties.Length); + Assert.IsNotNull(blob.Properties.ETag); + Assert.IsTrue(blob.Properties.LastModified > DateTimeOffset.UtcNow.AddMinutes(-5)); + Assert.IsNull(blob.Properties.CacheControl); + Assert.IsNull(blob.Properties.ContentDisposition); + Assert.IsNull(blob.Properties.ContentEncoding); + Assert.IsNull(blob.Properties.ContentLanguage); + Assert.IsNull(blob.Properties.ContentType); + Assert.IsNull(blob.Properties.ContentMD5); + Assert.AreEqual(LeaseStatus.Unspecified, blob.Properties.LeaseStatus); + Assert.AreEqual(BlobType.AppendBlob, blob.Properties.BlobType); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + result = blob2.BeginFetchAttributes( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob2.EndFetchAttributes(result); + Assert.AreEqual(0, blob2.Properties.Length); + Assert.AreEqual(blob.Properties.ETag, blob2.Properties.ETag); + Assert.AreEqual(blob.Properties.LastModified, blob2.Properties.LastModified); + Assert.IsNull(blob2.Properties.CacheControl); + Assert.IsNull(blob2.Properties.ContentDisposition); + Assert.IsNull(blob2.Properties.ContentEncoding); + Assert.IsNull(blob2.Properties.ContentLanguage); + Assert.AreEqual("application/octet-stream", blob2.Properties.ContentType); + Assert.IsNull(blob2.Properties.ContentMD5); + Assert.AreEqual(LeaseStatus.Unlocked, blob2.Properties.LeaseStatus); + Assert.AreEqual(BlobType.AppendBlob, blob2.Properties.BlobType); + } + } + finally + { + container.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Verify the attributes of a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobFetchAttributesTask() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.CreateAsync().Wait(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplaceAsync().Wait(); + Assert.AreEqual(0, blob.Properties.Length); + Assert.IsNotNull(blob.Properties.ETag); + Assert.IsTrue(blob.Properties.LastModified > DateTimeOffset.UtcNow.AddMinutes(-5)); + Assert.IsNull(blob.Properties.CacheControl); + Assert.IsNull(blob.Properties.ContentEncoding); + Assert.IsNull(blob.Properties.ContentLanguage); + Assert.IsNull(blob.Properties.ContentType); + Assert.IsNull(blob.Properties.ContentMD5); + Assert.AreEqual(LeaseStatus.Unspecified, blob.Properties.LeaseStatus); + Assert.AreEqual(BlobType.AppendBlob, blob.Properties.BlobType); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + blob2.FetchAttributesAsync().Wait(); + Assert.AreEqual(0, blob2.Properties.Length); + Assert.AreEqual(blob.Properties.ETag, blob2.Properties.ETag); + Assert.AreEqual(blob.Properties.LastModified, blob2.Properties.LastModified); + Assert.IsNull(blob2.Properties.CacheControl); + Assert.IsNull(blob2.Properties.ContentEncoding); + Assert.IsNull(blob2.Properties.ContentLanguage); + Assert.AreEqual("application/octet-stream", blob2.Properties.ContentType); + Assert.IsNull(blob2.Properties.ContentMD5); + Assert.AreEqual(LeaseStatus.Unlocked, blob2.Properties.LeaseStatus); + Assert.AreEqual(BlobType.AppendBlob, blob2.Properties.BlobType); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Verify setting the properties of a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobSetProperties() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + string eTag = blob.Properties.ETag; + DateTimeOffset lastModified = blob.Properties.LastModified.Value; + + Thread.Sleep(1000); + + blob.Properties.CacheControl = "no-transform"; + blob.Properties.ContentDisposition = "attachment"; + blob.Properties.ContentEncoding = "gzip"; + blob.Properties.ContentLanguage = "tr,en"; + blob.Properties.ContentMD5 = "MDAwMDAwMDA="; + blob.Properties.ContentType = "text/html"; + blob.SetProperties(); + Assert.IsTrue(blob.Properties.LastModified > lastModified); + Assert.AreNotEqual(eTag, blob.Properties.ETag); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + blob2.FetchAttributes(); + Assert.AreEqual("no-transform", blob2.Properties.CacheControl); + Assert.AreEqual("attachment", blob2.Properties.ContentDisposition); + Assert.AreEqual("gzip", blob2.Properties.ContentEncoding); + Assert.AreEqual("tr,en", blob2.Properties.ContentLanguage); + Assert.AreEqual("MDAwMDAwMDA=", blob2.Properties.ContentMD5); + Assert.AreEqual("text/html", blob2.Properties.ContentType); + + CloudAppendBlob blob3 = container.GetAppendBlobReference("blob1"); + using (MemoryStream stream = new MemoryStream()) + { + BlobRequestOptions options = new BlobRequestOptions() + { + DisableContentMD5Validation = true, + }; + blob3.DownloadToStream(stream, null, options); + } + AssertAreEqual(blob2.Properties, blob3.Properties); + + CloudAppendBlob blob4 = (CloudAppendBlob)container.ListBlobs().First(); + AssertAreEqual(blob2.Properties, blob4.Properties); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Verify setting the properties of a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobSetPropertiesAPM() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + IAsyncResult result = blob.BeginCreateOrReplace( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob.EndCreateOrReplace(result); + string eTag = blob.Properties.ETag; + DateTimeOffset lastModified = blob.Properties.LastModified.Value; + + Thread.Sleep(1000); + + blob.Properties.CacheControl = "no-transform"; + blob.Properties.ContentDisposition = "attachment"; + blob.Properties.ContentEncoding = "gzip"; + blob.Properties.ContentLanguage = "tr,en"; + blob.Properties.ContentMD5 = "MDAwMDAwMDA="; + blob.Properties.ContentType = "text/html"; + result = blob.BeginSetProperties( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob.EndSetProperties(result); + Assert.IsTrue(blob.Properties.LastModified > lastModified); + Assert.AreNotEqual(eTag, blob.Properties.ETag); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + result = blob2.BeginFetchAttributes( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob2.EndFetchAttributes(result); + Assert.AreEqual("no-transform", blob2.Properties.CacheControl); + Assert.AreEqual("attachment", blob2.Properties.ContentDisposition); + Assert.AreEqual("gzip", blob2.Properties.ContentEncoding); + Assert.AreEqual("tr,en", blob2.Properties.ContentLanguage); + Assert.AreEqual("MDAwMDAwMDA=", blob2.Properties.ContentMD5); + Assert.AreEqual("text/html", blob2.Properties.ContentType); + + CloudAppendBlob blob3 = container.GetAppendBlobReference("blob1"); + using (MemoryStream stream = new MemoryStream()) + { + BlobRequestOptions options = new BlobRequestOptions() + { + DisableContentMD5Validation = true, + }; + result = blob3.BeginDownloadToStream(stream, null, options, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob3.EndDownloadToStream(result); + } + AssertAreEqual(blob2.Properties, blob3.Properties); + + result = container.BeginListBlobsSegmented(null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + BlobResultSegment results = container.EndListBlobsSegmented(result); + CloudAppendBlob blob4 = (CloudAppendBlob)results.Results.First(); + AssertAreEqual(blob2.Properties, blob4.Properties); + } + } + finally + { + container.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Verify setting the properties of a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobSetPropertiesTask() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.CreateAsync().Wait(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplaceAsync().Wait(); + string eTag = blob.Properties.ETag; + DateTimeOffset lastModified = blob.Properties.LastModified.Value; + + Thread.Sleep(1000); + + blob.Properties.CacheControl = "no-transform"; + blob.Properties.ContentEncoding = "gzip"; + blob.Properties.ContentLanguage = "tr,en"; + blob.Properties.ContentMD5 = "MDAwMDAwMDA="; + blob.Properties.ContentType = "text/html"; + blob.SetPropertiesAsync().Wait(); + Assert.IsTrue(blob.Properties.LastModified > lastModified); + Assert.AreNotEqual(eTag, blob.Properties.ETag); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + blob2.FetchAttributesAsync().Wait(); + Assert.AreEqual("no-transform", blob2.Properties.CacheControl); + Assert.AreEqual("gzip", blob2.Properties.ContentEncoding); + Assert.AreEqual("tr,en", blob2.Properties.ContentLanguage); + Assert.AreEqual("MDAwMDAwMDA=", blob2.Properties.ContentMD5); + Assert.AreEqual("text/html", blob2.Properties.ContentType); + + CloudAppendBlob blob3 = container.GetAppendBlobReference("blob1"); + using (MemoryStream stream = new MemoryStream()) + { + BlobRequestOptions options = new BlobRequestOptions() + { + DisableContentMD5Validation = true, + }; + blob3.DownloadToStreamAsync(stream, null, options, null).Wait(); + } + AssertAreEqual(blob2.Properties, blob3.Properties); + + CloudAppendBlob blob4 = (CloudAppendBlob)container.ListBlobsSegmentedAsync(null).Result.Results.First(); + AssertAreEqual(blob2.Properties, blob4.Properties); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Try retrieving properties of an append blob using a block blob reference")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobFetchAttributesInvalidType() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + + CloudBlockBlob blob2 = container.GetBlockBlobReference("blob1"); + StorageException e = TestHelper.ExpectedException( + () => blob2.FetchAttributes(), + "Fetching attributes of an append blob using block blob reference should fail"); + Assert.IsInstanceOfType(e.InnerException, typeof(InvalidOperationException)); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Verify that creating an append blob can also set its metadata")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobCreateWithMetadata() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.Metadata["key1"] = "value1"; + blob.CreateOrReplace(); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + blob2.FetchAttributes(); + Assert.AreEqual(1, blob2.Metadata.Count); + Assert.AreEqual("value1", blob2.Metadata["key1"]); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Verify that empty metadata on an append blob can be retrieved.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobGetEmptyMetadata() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + blob.Metadata["key1"] = "value1"; + + OperationContext context = new OperationContext(); + context.SendingRequest += (sender, e) => + { + e.Request.Headers["x-ms-meta-key1"] = string.Empty; + }; + + blob.SetMetadata(operationContext: context); + blob2.FetchAttributes(); + Assert.AreEqual(1, blob2.Metadata.Count); + Assert.AreEqual(string.Empty, blob2.Metadata["key1"]); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Verify that an append blob's metadata can be updated")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobSetMetadata() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + blob2.FetchAttributes(); + Assert.AreEqual(0, blob2.Metadata.Count); + + blob.Metadata["key1"] = null; + StorageException e = TestHelper.ExpectedException( + () => blob.SetMetadata(), + "Metadata keys should have a non-null value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = ""; + e = TestHelper.ExpectedException( + () => blob.SetMetadata(), + "Metadata keys should have a non-empty value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = " "; + e = TestHelper.ExpectedException( + () => blob.SetMetadata(), + "Metadata keys should have a non-whitespace only value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = "value1"; + blob.SetMetadata(); + + blob2.FetchAttributes(); + Assert.AreEqual(1, blob2.Metadata.Count); + Assert.AreEqual("value1", blob2.Metadata["key1"]); + + CloudAppendBlob blob3 = (CloudAppendBlob)container.ListBlobs(null, true, BlobListingDetails.Metadata, null, null).First(); + Assert.AreEqual(1, blob3.Metadata.Count); + Assert.AreEqual("value1", blob3.Metadata["key1"]); + + blob.Metadata.Clear(); + blob.SetMetadata(); + + blob2.FetchAttributes(); + Assert.AreEqual(0, blob2.Metadata.Count); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Verify that an append blob's metadata can be updated")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobSetMetadataAPM() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + IAsyncResult result = blob2.BeginFetchAttributes( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob2.EndFetchAttributes(result); + Assert.AreEqual(0, blob2.Metadata.Count); + + blob.Metadata["key1"] = null; + result = blob.BeginSetMetadata( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + Exception e = TestHelper.ExpectedException( + () => blob.EndSetMetadata(result), + "Metadata keys should have a non-null value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = ""; + result = blob.BeginSetMetadata( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + e = TestHelper.ExpectedException( + () => blob.EndSetMetadata(result), + "Metadata keys should have a non-empty value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = " "; + result = blob.BeginSetMetadata( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + e = TestHelper.ExpectedException( + () => blob.EndSetMetadata(result), + "Metadata keys should have a non-whitespace only value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = "value1"; + result = blob.BeginSetMetadata( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob.EndSetMetadata(result); + + result = blob2.BeginFetchAttributes( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob2.EndFetchAttributes(result); + Assert.AreEqual(1, blob2.Metadata.Count); + Assert.AreEqual("value1", blob2.Metadata["key1"]); + + result = container.BeginListBlobsSegmented(null, true, BlobListingDetails.Metadata, null, null, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + BlobResultSegment results = container.EndListBlobsSegmented(result); + CloudAppendBlob blob3 = (CloudAppendBlob)results.Results.First(); + Assert.AreEqual(1, blob3.Metadata.Count); + Assert.AreEqual("value1", blob3.Metadata["key1"]); + + blob.Metadata.Clear(); + result = blob.BeginSetMetadata( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob.EndSetMetadata(result); + + result = blob2.BeginFetchAttributes( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob2.EndFetchAttributes(result); + Assert.AreEqual(0, blob2.Metadata.Count); + } + } + finally + { + container.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Verify that a append blob's metadata can be updated")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobSetMetadataTask() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.CreateAsync().Wait(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplaceAsync().Wait(); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + blob2.FetchAttributesAsync().Wait(); + Assert.AreEqual(0, blob2.Metadata.Count); + + blob.Metadata["key1"] = null; + StorageException e = TestHelper.ExpectedExceptionTask( + blob.SetMetadataAsync(), + "Metadata keys should have a non-null value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = ""; + e = TestHelper.ExpectedExceptionTask( + blob.SetMetadataAsync(), + "Metadata keys should have a non-empty value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = " "; + e = TestHelper.ExpectedExceptionTask( + blob.SetMetadataAsync(), + "Metadata keys should have a non-whitespace only value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = "value1"; + blob.SetMetadataAsync().Wait(); + + blob2.FetchAttributesAsync().Wait(); + Assert.AreEqual(1, blob2.Metadata.Count); + Assert.AreEqual("value1", blob2.Metadata["key1"]); + + CloudAppendBlob blob3 = + (CloudAppendBlob) + container.ListBlobsSegmentedAsync(null, true, BlobListingDetails.Metadata, null, null, null, null) + .Result + .Results + .First(); + + Assert.AreEqual(1, blob3.Metadata.Count); + Assert.AreEqual("value1", blob3.Metadata["key1"]); + + blob.Metadata.Clear(); + blob.SetMetadataAsync().Wait(); + + blob2.FetchAttributesAsync().Wait(); + Assert.AreEqual(0, blob2.Metadata.Count); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobAppendBlock() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.Create(); + try + { + this.CloudAppendBlock(container, 6 * 512, null, 0, false); + this.CloudAppendBlock(container, 6 * 512, null, 1024, false); + } + finally + { + container.Delete(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobAppendBlockAPM() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.Create(); + try + { + this.CloudAppendBlock(container, 2 * 1024, null, 0, true); + this.CloudAppendBlock(container, 2 * 1024, null, 1024, true); + } + finally + { + container.Delete(); + } + } + +#if TASK + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobAppendBlockTask() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.CreateAsync().Wait(); + try + { + this.CloudAppendBlockTask(container, 2 * 1024, null, 0); + this.CloudAppendBlockTask(container, 2 * 1024, null, 1024); + } + finally + { + container.DeleteAsync().Wait(); + } + } +#endif + + private void CloudAppendBlock(CloudBlobContainer container, int size, AccessCondition accessCondition, int startOffset, bool isAsync) + { + byte[] buffer = GetRandomBuffer(size); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + + using (MemoryStream originalBlob = new MemoryStream()) + { + originalBlob.Write(buffer, startOffset, buffer.Length - startOffset); + + using (MemoryStream sourceStream = new MemoryStream(buffer)) + { + sourceStream.Seek(startOffset, SeekOrigin.Begin); + BlobRequestOptions options = new BlobRequestOptions() + { + UseTransactionalMD5 = true, + }; + if (isAsync) + { + using (ManualResetEvent waitHandle = new ManualResetEvent(false)) + { + + ICancellableAsyncResult result = blob.BeginAppendBlock( + sourceStream, null, accessCondition, options, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndAppendBlock(result); + } + } + else + { + blob.AppendBlock(sourceStream, null, accessCondition, options); + } + } + + blob.FetchAttributes(); + + using (MemoryStream downloadedBlob = new MemoryStream()) + { + if (isAsync) + { + using (ManualResetEvent waitHandle = new ManualResetEvent(false)) + { + ICancellableAsyncResult result = blob.BeginDownloadToStream(downloadedBlob, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob.EndDownloadToStream(result); + } + } + else + { + blob.DownloadToStream(downloadedBlob); + } + + TestHelper.AssertStreamsAreEqualAtIndex( + originalBlob, + downloadedBlob, + 0, + 0, + (int)originalBlob.Length); + } + } + } + +#if TASK + private void CloudAppendBlockTask(CloudBlobContainer container, int size, AccessCondition accessCondition, int startOffset) + { + try + { + byte[] buffer = GetRandomBuffer(size); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplaceAsync().Wait(); + + using (MemoryStream originalBlob = new MemoryStream()) + { + originalBlob.Write(buffer, startOffset, buffer.Length - startOffset); + + using (MemoryStream sourceStream = new MemoryStream(buffer)) + { + sourceStream.Seek(startOffset, SeekOrigin.Begin); + BlobRequestOptions options = new BlobRequestOptions() + { + UseTransactionalMD5 = true, + }; + + blob.AppendBlockAsync(sourceStream, null, accessCondition, options, null).Wait(); + } + + blob.FetchAttributesAsync().Wait(); + + using (MemoryStream downloadedBlob = new MemoryStream()) + { + blob.DownloadToStreamAsync(downloadedBlob).Wait(); + + TestHelper.AssertStreamsAreEqualAtIndex( + originalBlob, + downloadedBlob, + 0, + 0, + (int)originalBlob.Length); + } + } + } + catch (AggregateException ex) + { + throw ex.InnerException; + } + } +#endif + + [TestMethod] + [Description("Validate UploadFromStream.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobValidateUploadFromStream() + { + byte[] buffer = GetRandomBuffer(6 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + + blob.UploadFromStream(new MemoryStream(buffer)); + blob.FetchAttributes(); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + blob.UploadFromStream(new MemoryStream(buffer)); + blob.FetchAttributes(); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + blob.UploadFromStream(new MemoryStream(buffer), null /* accessCondition */, null /* options */, null /* operationContext */); + blob.FetchAttributes(); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Validate UploadFromStream.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobValidateUploadFromStreamAPM() + { + byte[] buffer = GetRandomBuffer(6 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + IAsyncResult result; + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + using (MemoryStream memStream = new MemoryStream(buffer)) + { + result = blob.BeginUploadFromStream( + memStream, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndUploadFromStream(result); + + result = blob.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndFetchAttributes(result); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + + result = blob.BeginUploadFromStream( + memStream, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndUploadFromStream(result); + + result = blob.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndFetchAttributes(result); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + + result = blob.BeginUploadFromStream( + memStream, null /* accessCondition */, null /* options */, null /* operationContext */, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndUploadFromStream(result); + + result = blob.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndFetchAttributes(result); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + } + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Validate AppendFromStream.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobValidateAppendFromStream() + { + // Every time append a buffer that is bigger than a single block. + byte[] buffer = GetRandomBuffer(6 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + + blob.AppendFromStream(new MemoryStream(buffer)); + blob.FetchAttributes(); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + blob.AppendFromStream(new MemoryStream(buffer)); + blob.FetchAttributes(); + Assert.AreEqual(12 * 1024 * 1024, blob.Properties.Length); + + blob.AppendFromStream(new MemoryStream(buffer), null /* accessCondition */, null /* options */, null /* operationContext */); + blob.FetchAttributes(); + Assert.AreEqual(18 * 1024 * 1024, blob.Properties.Length); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Validate AppendFromStream.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobValidateAppendFromStreamWithLength() + { + // Every time append a buffer that is bigger than a single block. + byte[] buffer = GetRandomBuffer(6 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + + blob.AppendFromStream(new MemoryStream(buffer), 5 * 1024 * 1024); + blob.FetchAttributes(); + Assert.AreEqual(5 * 1024 * 1024, blob.Properties.Length); + + blob.AppendFromStream(new MemoryStream(buffer), 5 * 1024 * 1024); + blob.FetchAttributes(); + Assert.AreEqual(10 * 1024 * 1024, blob.Properties.Length); + + blob.AppendFromStream(new MemoryStream(buffer), 5 * 1024 * 1024, null /* accessCondition */, null /* options */, null /* operationContext */); + blob.FetchAttributes(); + Assert.AreEqual(15 * 1024 * 1024, blob.Properties.Length); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Validate AppendFromStream.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobValidateAppendFromStreamAPM() + { + // Every time append a buffer that is bigger than a single block. + byte[] buffer = GetRandomBuffer(6 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + IAsyncResult result; + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + using (MemoryStream memStream = new MemoryStream(buffer)) + { + result = blob.BeginCreateOrReplace(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndCreateOrReplace(result); + + result = blob.BeginAppendFromStream( + memStream, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndAppendFromStream(result); + + result = blob.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndFetchAttributes(result); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + + result = blob.BeginAppendFromStream( + memStream, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndAppendFromStream(result); + + result = blob.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndFetchAttributes(result); + Assert.AreEqual(12 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + + result = blob.BeginAppendFromStream( + memStream, null /* accessCondition */, null /* options */, null /* operationContext */, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndAppendFromStream(result); + + result = blob.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndFetchAttributes(result); + Assert.AreEqual(18 * 1024 * 1024, blob.Properties.Length); + } + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Validate AppendFromStream.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobValidateAppendFromStreamWithLengthAPM() + { + // Every time append a buffer that is bigger than a single block. + byte[] buffer = GetRandomBuffer(6 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + IAsyncResult result; + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + using (MemoryStream memStream = new MemoryStream(buffer)) + { + result = blob.BeginCreateOrReplace(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndCreateOrReplace(result); + + result = blob.BeginAppendFromStream( + memStream, 5 * 1024 * 1024, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndAppendFromStream(result); + + result = blob.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndFetchAttributes(result); + Assert.AreEqual(5 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + + result = blob.BeginAppendFromStream( + memStream, 5 * 1024 * 1024, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndAppendFromStream(result); + + result = blob.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndFetchAttributes(result); + Assert.AreEqual(10 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + + result = blob.BeginAppendFromStream( + memStream, 5 * 1024 * 1024, null /* accessCondition */, null /* options */, null /* operationContext */, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndAppendFromStream(result); + + result = blob.BeginFetchAttributes(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndFetchAttributes(result); + Assert.AreEqual(15 * 1024 * 1024, blob.Properties.Length); + } + } + } + finally + { + container.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Validate AppendFromStream.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobValidateAppendFromStreamTask() + { + byte[] buffer = GetRandomBuffer(6 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.CreateAsync().Wait(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplaceAsync().Wait(); + + using (MemoryStream memStream = new MemoryStream(buffer)) + { + blob.AppendFromStreamAsync(memStream).Wait(); + blob.FetchAttributesAsync().Wait(); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + blob.AppendFromStreamAsync(memStream, CancellationToken.None).Wait(); + blob.FetchAttributesAsync().Wait(); + Assert.AreEqual(12 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + blob.AppendFromStreamAsync(memStream, null /* accessCondition */, null /* options */, null /* operationContext */).Wait(); + blob.FetchAttributesAsync().Wait(); + Assert.AreEqual(18 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + blob.AppendFromStreamAsync(memStream, null /* accessCondition */, null /* options */, null /* operationContext */, CancellationToken.None).Wait(); + blob.FetchAttributesAsync().Wait(); + Assert.AreEqual(24 * 1024 * 1024, blob.Properties.Length); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Validate AppendFromStream.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobValidateAppendFromStreamWithLengthTask() + { + byte[] buffer = GetRandomBuffer(6 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.CreateAsync().Wait(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplaceAsync().Wait(); + + using (MemoryStream memStream = new MemoryStream(buffer)) + { + blob.AppendFromStreamAsync(memStream, 5 * 1024 * 1024).Wait(); + blob.FetchAttributesAsync().Wait(); + Assert.AreEqual(5 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + blob.AppendFromStreamAsync(memStream, 5 * 1024 * 1024, CancellationToken.None).Wait(); + blob.FetchAttributesAsync().Wait(); + Assert.AreEqual(10 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + blob.AppendFromStreamAsync(memStream, 5 * 1024 * 1024, null /* accessCondition */, null /* options */, null /* operationContext */).Wait(); + blob.FetchAttributesAsync().Wait(); + Assert.AreEqual(15 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + blob.AppendFromStreamAsync(memStream, 5 * 1024 * 1024, null /* accessCondition */, null /* options */, null /* operationContext */, CancellationToken.None).Wait(); + blob.FetchAttributesAsync().Wait(); + Assert.AreEqual(20 * 1024 * 1024, blob.Properties.Length); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Verify the append offset returned by the service.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobVerifyAppendOffset() + { + byte[] buffer = GetRandomBuffer(2 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + + long offset = blob.AppendBlock(new MemoryStream(buffer)); + Assert.AreEqual(0, offset); + + offset = blob.AppendBlock(new MemoryStream(buffer)); + Assert.AreEqual(2 * 1024 * 1024, offset); + + offset = blob.AppendBlock(new MemoryStream(buffer)); + Assert.AreEqual(4 * 1024 * 1024, offset); + + offset = blob.AppendBlock(new MemoryStream(buffer)); + Assert.AreEqual(6 * 1024 * 1024, offset); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Verify the append offset returned by the service.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobVerifyAppendOffsetAPM() + { + byte[] buffer = GetRandomBuffer(2 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + IAsyncResult result; + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + using (MemoryStream memStream = new MemoryStream(buffer)) + { + result = blob.BeginCreateOrReplace(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndCreateOrReplace(result); + + result = blob.BeginAppendBlock( + memStream, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + long offset = blob.EndAppendBlock(result); + Assert.AreEqual(0, offset); + + memStream.Seek(0, SeekOrigin.Begin); + result = blob.BeginAppendBlock( + memStream, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + offset = blob.EndAppendBlock(result); + Assert.AreEqual(2 * 1024 * 1024, offset); + + memStream.Seek(0, SeekOrigin.Begin); + result = blob.BeginAppendBlock( + memStream, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + offset = blob.EndAppendBlock(result); + Assert.AreEqual(4 * 1024 * 1024, offset); + + memStream.Seek(0, SeekOrigin.Begin); + result = blob.BeginAppendBlock( + memStream, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + offset = blob.EndAppendBlock(result); + Assert.AreEqual(6 * 1024 * 1024, offset); + } + } + } + finally + { + container.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Verify the append offset returned by the service.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobVerifyAppendOffsetTask() + { + byte[] buffer = GetRandomBuffer(2 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.CreateAsync().Wait(); + + MemoryStream originalData = new MemoryStream(GetRandomBuffer(1024)); + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplaceAsync().Wait(); + + using (MemoryStream memStream = new MemoryStream(buffer)) + { + Assert.AreEqual(0, blob.AppendBlockAsync(new MemoryStream(buffer)).Result); + memStream.Seek(0, SeekOrigin.Begin); + Assert.AreEqual(2 * 1024 * 1024, blob.AppendBlockAsync(new MemoryStream(buffer)).Result); + memStream.Seek(0, SeekOrigin.Begin); + Assert.AreEqual(4 * 1024 * 1024, blob.AppendBlockAsync(new MemoryStream(buffer)).Result); + memStream.Seek(0, SeekOrigin.Begin); + Assert.AreEqual(6 * 1024 * 1024, blob.AppendBlockAsync(new MemoryStream(buffer)).Result); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Upload and download text")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadText() + { + this.DoTextUploadDownload("test", false, false); + this.DoTextUploadDownload("char中文test", true, false); + } + + [TestMethod] + [Description("Upload and download text")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadTextAPM() + { + this.DoTextUploadDownload("test", false, true); + this.DoTextUploadDownload("char中文test", true, true); + } + +#if TASK + [TestMethod] + [Description("Upload and download text")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadTextTask() + { + this.DoTextUploadDownloadTask("test", false); + this.DoTextUploadDownloadTask("char中文test", true); + } +#endif + + private void DoTextUploadDownload(string text, bool checkDifferentEncoding, bool isAsync) + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.CreateIfNotExists(); + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + + if (isAsync) + { + IAsyncResult result; + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + result = blob.BeginUploadText(text, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob.EndUploadText(result); + result = blob.BeginDownloadText( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + Assert.AreEqual(text, blob.EndDownloadText(result)); + if (checkDifferentEncoding) + { + result = blob.BeginDownloadText(Encoding.Unicode, null, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + Assert.AreNotEqual(text, blob.EndDownloadText(result)); + } + + OperationContext context = new OperationContext(); + result = blob.BeginUploadText(text, Encoding.Unicode, null, null, context, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob.EndUploadText(result); + + // 3 because of Create and Appendblock + Assert.AreEqual(2, context.RequestResults.Count); + result = blob.BeginDownloadText(Encoding.Unicode, null, null, context, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + Assert.AreEqual(text, blob.EndDownloadText(result)); + Assert.AreEqual(3, context.RequestResults.Count); + if (checkDifferentEncoding) + { + result = blob.BeginDownloadText( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + Assert.AreNotEqual(text, blob.EndDownloadText(result)); + } + } + } + else + { + blob.UploadText(text); + Assert.AreEqual(text, blob.DownloadText()); + if (checkDifferentEncoding) + { + Assert.AreNotEqual(text, blob.DownloadText(Encoding.Unicode)); + } + + blob.UploadText(text, Encoding.Unicode); + Assert.AreEqual(text, blob.DownloadText(Encoding.Unicode)); + if (checkDifferentEncoding) + { + Assert.AreNotEqual(text, blob.DownloadText()); + } + + OperationContext context = new OperationContext(); + blob.UploadText(text, Encoding.Unicode, null, null, context); + + // 3 because of Create and Appendblock + Assert.AreEqual(2, context.RequestResults.Count); + blob.DownloadText(Encoding.Unicode, null, null, context); + Assert.AreEqual(3, context.RequestResults.Count); + } + } + finally + { + container.DeleteIfExists(); + } + } + +#if TASK + private void DoTextUploadDownloadTask(string text, bool checkDifferentEncoding) + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.CreateIfNotExistsAsync().Wait(); + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + + blob.UploadTextAsync(text).Wait(); + Assert.AreEqual(text, blob.DownloadTextAsync().Result); + if (checkDifferentEncoding) + { + Assert.AreNotEqual(text, blob.DownloadTextAsync(Encoding.Unicode, null, null, null).Result); + } + + blob.UploadTextAsync(text, Encoding.Unicode, null, null, null).Wait(); + Assert.AreEqual(text, blob.DownloadTextAsync(Encoding.Unicode, null, null, null).Result); + if (checkDifferentEncoding) + { + Assert.AreNotEqual(text, blob.DownloadTextAsync().Result); + } + + OperationContext context = new OperationContext(); + blob.UploadTextAsync(text, Encoding.Unicode, null, null, context).Wait(); + + // 3 because of Create and Appendblock + Assert.AreEqual(2, context.RequestResults.Count); + blob.DownloadTextAsync(Encoding.Unicode, null, null, context).Wait(); + Assert.AreEqual(3, context.RequestResults.Count); + } + finally + { + container.DeleteIfExists(); + } + } +#endif + + [TestMethod] + [Description("Create snapshots of a append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobSnapshot() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + MemoryStream originalData = new MemoryStream(GetRandomBuffer(1024)); + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + blob.AppendBlock(originalData, null); + Assert.IsFalse(blob.IsSnapshot); + Assert.IsNull(blob.SnapshotTime, "Root blob has SnapshotTime set"); + Assert.IsFalse(blob.SnapshotQualifiedUri.Query.Contains("snapshot")); + Assert.AreEqual(blob.Uri, blob.SnapshotQualifiedUri); + + CloudAppendBlob snapshot1 = blob.CreateSnapshot(); + Assert.AreEqual(blob.Properties.ETag, snapshot1.Properties.ETag); + Assert.AreEqual(blob.Properties.LastModified, snapshot1.Properties.LastModified); + Assert.IsTrue(snapshot1.IsSnapshot); + Assert.IsNotNull(snapshot1.SnapshotTime, "Snapshot does not have SnapshotTime set"); + Assert.AreEqual(blob.Uri, snapshot1.Uri); + Assert.AreNotEqual(blob.SnapshotQualifiedUri, snapshot1.SnapshotQualifiedUri); + Assert.AreNotEqual(snapshot1.Uri, snapshot1.SnapshotQualifiedUri); + Assert.IsTrue(snapshot1.SnapshotQualifiedUri.Query.Contains("snapshot")); + + CloudAppendBlob snapshot2 = blob.CreateSnapshot(); + Assert.IsTrue(snapshot2.SnapshotTime.Value > snapshot1.SnapshotTime.Value); + + snapshot1.FetchAttributes(); + snapshot2.FetchAttributes(); + blob.FetchAttributes(); + AssertAreEqual(snapshot1.Properties, blob.Properties, false); + + CloudAppendBlob snapshot1Clone = new CloudAppendBlob(new Uri(blob.Uri + "?snapshot=" + snapshot1.SnapshotTime.Value.ToString("O")), blob.ServiceClient.Credentials); + Assert.IsNotNull(snapshot1Clone.SnapshotTime, "Snapshot clone does not have SnapshotTime set"); + Assert.AreEqual(snapshot1.SnapshotTime.Value, snapshot1Clone.SnapshotTime.Value); + snapshot1Clone.FetchAttributes(); + AssertAreEqual(snapshot1.Properties, snapshot1Clone.Properties, false); + + CloudAppendBlob snapshotCopy = container.GetAppendBlobReference("blob2"); + snapshotCopy.StartCopy(TestHelper.Defiddler(snapshot1.Uri)); + WaitForCopy(snapshotCopy); + Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); + + using (Stream snapshotStream = snapshot1.OpenRead()) + { + snapshotStream.Seek(0, SeekOrigin.End); + TestHelper.AssertStreamsAreEqual(originalData, snapshotStream); + } + + blob.CreateOrReplace(); + + using (Stream snapshotStream = snapshot1.OpenRead()) + { + snapshotStream.Seek(0, SeekOrigin.End); + TestHelper.AssertStreamsAreEqual(originalData, snapshotStream); + } + + List blobs = container.ListBlobs(null, true, BlobListingDetails.All, null, null).ToList(); + Assert.AreEqual(4, blobs.Count); + AssertAreEqual(snapshot1, (CloudBlob)blobs[0]); + AssertAreEqual(snapshot2, (CloudBlob)blobs[1]); + AssertAreEqual(blob, (CloudBlob)blobs[2]); + AssertAreEqual(snapshotCopy, (CloudBlob)blobs[3]); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Create snapshots of an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobSnapshotAPM() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + MemoryStream originalData = new MemoryStream(GetRandomBuffer(1024)); + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + IAsyncResult result; + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + result = blob.BeginCreateOrReplace(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndCreateOrReplace(result); + + result = blob.BeginAppendBlock(originalData, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndAppendBlock(result); + Assert.IsFalse(blob.IsSnapshot); + Assert.IsNull(blob.SnapshotTime, "Root blob has SnapshotTime set"); + Assert.IsFalse(blob.SnapshotQualifiedUri.Query.Contains("snapshot")); + Assert.AreEqual(blob.Uri, blob.SnapshotQualifiedUri); + + result = blob.BeginCreateSnapshot(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + CloudAppendBlob snapshot1 = blob.EndCreateSnapshot(result); + Assert.AreEqual(blob.Properties.ETag, snapshot1.Properties.ETag); + Assert.AreEqual(blob.Properties.LastModified, snapshot1.Properties.LastModified); + Assert.IsTrue(snapshot1.IsSnapshot); + Assert.IsNotNull(snapshot1.SnapshotTime, "Snapshot does not have SnapshotTime set"); + Assert.AreEqual(blob.Uri, snapshot1.Uri); + Assert.AreNotEqual(blob.SnapshotQualifiedUri, snapshot1.SnapshotQualifiedUri); + Assert.AreNotEqual(snapshot1.Uri, snapshot1.SnapshotQualifiedUri); + Assert.IsTrue(snapshot1.SnapshotQualifiedUri.Query.Contains("snapshot")); + + result = blob.BeginCreateSnapshot(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + CloudAppendBlob snapshot2 = blob.EndCreateSnapshot(result); + Assert.IsTrue(snapshot2.SnapshotTime.Value > snapshot1.SnapshotTime.Value); + + snapshot1.FetchAttributes(); + snapshot2.FetchAttributes(); + blob.FetchAttributes(); + AssertAreEqual(snapshot1.Properties, blob.Properties); + + CloudAppendBlob snapshotCopy = container.GetAppendBlobReference("blob2"); + result = snapshotCopy.BeginStartCopy(snapshot1, null, null, null, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + snapshotCopy.EndStartCopy(result); + WaitForCopy(snapshotCopy); + Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); + + result = snapshot1.BeginOpenRead(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + using (Stream snapshotStream = snapshot1.EndOpenRead(result)) + { + snapshotStream.Seek(0, SeekOrigin.End); + TestHelper.AssertStreamsAreEqual(originalData, snapshotStream); + } + + result = blob.BeginCreateOrReplace(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndCreateOrReplace(result); + + result = snapshot1.BeginOpenRead(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + using (Stream snapshotStream = snapshot1.EndOpenRead(result)) + { + snapshotStream.Seek(0, SeekOrigin.End); + TestHelper.AssertStreamsAreEqual(originalData, snapshotStream); + } + + List blobs = container.ListBlobs(null, true, BlobListingDetails.All, null, null).ToList(); + Assert.AreEqual(4, blobs.Count); + AssertAreEqual(snapshot1, (CloudBlob)blobs[0]); + AssertAreEqual(snapshot2, (CloudBlob)blobs[1]); + AssertAreEqual(blob, (CloudBlob)blobs[2]); + AssertAreEqual(snapshotCopy, (CloudBlob)blobs[3]); + } + } + finally + { + container.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Create snapshots of an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobSnapshotTask() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.CreateAsync().Wait(); + + MemoryStream originalData = new MemoryStream(GetRandomBuffer(1024)); + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplaceAsync().Wait(); + blob.AppendBlockAsync(originalData, null).Wait(); + Assert.IsFalse(blob.IsSnapshot); + Assert.IsNull(blob.SnapshotTime, "Root blob has SnapshotTime set"); + Assert.IsFalse(blob.SnapshotQualifiedUri.Query.Contains("snapshot")); + Assert.AreEqual(blob.Uri, blob.SnapshotQualifiedUri); + + CloudAppendBlob snapshot1 = blob.CreateSnapshotAsync().Result; + Assert.AreEqual(blob.Properties.ETag, snapshot1.Properties.ETag); + Assert.AreEqual(blob.Properties.LastModified, snapshot1.Properties.LastModified); + Assert.IsTrue(snapshot1.IsSnapshot); + Assert.IsNotNull(snapshot1.SnapshotTime, "Snapshot does not have SnapshotTime set"); + Assert.AreEqual(blob.Uri, snapshot1.Uri); + Assert.AreNotEqual(blob.SnapshotQualifiedUri, snapshot1.SnapshotQualifiedUri); + Assert.AreNotEqual(snapshot1.Uri, snapshot1.SnapshotQualifiedUri); + Assert.IsTrue(snapshot1.SnapshotQualifiedUri.Query.Contains("snapshot")); + + CloudAppendBlob snapshot2 = blob.CreateSnapshotAsync().Result; + Assert.IsTrue(snapshot2.SnapshotTime.Value > snapshot1.SnapshotTime.Value); + + snapshot1.FetchAttributesAsync().Wait(); + snapshot2.FetchAttributesAsync().Wait(); + blob.FetchAttributesAsync().Wait(); + AssertAreEqual(snapshot1.Properties, blob.Properties); + + CloudAppendBlob snapshot1Clone = new CloudAppendBlob(new Uri(blob.Uri + "?snapshot=" + snapshot1.SnapshotTime.Value.ToString("O")), blob.ServiceClient.Credentials); + Assert.IsNotNull(snapshot1Clone.SnapshotTime, "Snapshot clone does not have SnapshotTime set"); + Assert.AreEqual(snapshot1.SnapshotTime.Value, snapshot1Clone.SnapshotTime.Value); + snapshot1Clone.FetchAttributesAsync().Wait(); + AssertAreEqual(snapshot1.Properties, snapshot1Clone.Properties); + + CloudAppendBlob snapshotCopy = container.GetAppendBlobReference("blob2"); + snapshotCopy.StartCopyAsync(snapshot1, null, null, null, null).Wait(); + WaitForCopy(snapshotCopy); + Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); + + using (Stream snapshotStream = snapshot1.OpenReadAsync().Result) + { + snapshotStream.Seek(0, SeekOrigin.End); + TestHelper.AssertStreamsAreEqual(originalData, snapshotStream); + } + + blob.CreateOrReplaceAsync().Wait(); + + using (Stream snapshotStream = snapshot1.OpenReadAsync().Result) + { + snapshotStream.Seek(0, SeekOrigin.End); + TestHelper.AssertStreamsAreEqual(originalData, snapshotStream); + } + + List blobs = + container.ListBlobsSegmentedAsync(null, true, BlobListingDetails.All, null, null, null, null) + .Result + .Results + .ToList(); + Assert.AreEqual(4, blobs.Count); + AssertAreEqual(snapshot1, (CloudBlob)blobs[0]); + AssertAreEqual(snapshot2, (CloudBlob)blobs[1]); + AssertAreEqual(blob, (CloudBlob)blobs[2]); + AssertAreEqual(snapshotCopy, (CloudBlob)blobs[3]); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Create a snapshot with explicit metadata")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobSnapshotMetadata() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + + blob.Metadata["Hello"] = "World"; + blob.Metadata["Marco"] = "Polo"; + blob.SetMetadata(); + + IDictionary snapshotMetadata = new Dictionary(); + snapshotMetadata["Hello"] = "Dolly"; + snapshotMetadata["Yoyo"] = "Ma"; + + CloudAppendBlob snapshot = blob.CreateSnapshot(snapshotMetadata); + + // Test the client view against the expected metadata + // None of the original metadata should be present + Assert.AreEqual("Dolly", snapshot.Metadata["Hello"]); + Assert.AreEqual("Ma", snapshot.Metadata["Yoyo"]); + Assert.IsFalse(snapshot.Metadata.ContainsKey("Marco")); + + // Test the server view against the expected metadata + snapshot.FetchAttributes(); + Assert.AreEqual("Dolly", snapshot.Metadata["Hello"]); + Assert.AreEqual("Ma", snapshot.Metadata["Yoyo"]); + Assert.IsFalse(snapshot.Metadata.ContainsKey("Marco")); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Create a snapshot with explicit metadata")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobSnapshotMetadataAPM() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + + blob.Metadata["Hello"] = "World"; + blob.Metadata["Marco"] = "Polo"; + blob.SetMetadata(); + + IDictionary snapshotMetadata = new Dictionary(); + snapshotMetadata["Hello"] = "Dolly"; + snapshotMetadata["Yoyo"] = "Ma"; + + IAsyncResult result; + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + result = blob.BeginCreateSnapshot(snapshotMetadata, null, null, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + CloudAppendBlob snapshot = blob.EndCreateSnapshot(result); + + // Test the client view against the expected metadata + // None of the original metadata should be present + Assert.AreEqual("Dolly", snapshot.Metadata["Hello"]); + Assert.AreEqual("Ma", snapshot.Metadata["Yoyo"]); + Assert.IsFalse(snapshot.Metadata.ContainsKey("Marco")); + + // Test the server view against the expected metadata + snapshot.FetchAttributes(); + Assert.AreEqual("Dolly", snapshot.Metadata["Hello"]); + Assert.AreEqual("Ma", snapshot.Metadata["Yoyo"]); + Assert.IsFalse(snapshot.Metadata.ContainsKey("Marco")); + } + } + finally + { + container.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Create a snapshot with explicit metadata")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobSnapshotMetadataTask() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.CreateAsync().Wait(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplaceAsync(null, null, new OperationContext()).Wait(); + + blob.Metadata["Hello"] = "World"; + blob.Metadata["Marco"] = "Polo"; + blob.SetMetadataAsync().Wait(); + + IDictionary snapshotMetadata = new Dictionary(); + snapshotMetadata["Hello"] = "Dolly"; + snapshotMetadata["Yoyo"] = "Ma"; + + CloudAppendBlob snapshot = blob.CreateSnapshotAsync(snapshotMetadata, null, null, null).Result; + + // Test the client view against the expected metadata + // None of the original metadata should be present + Assert.AreEqual("Dolly", snapshot.Metadata["Hello"]); + Assert.AreEqual("Ma", snapshot.Metadata["Yoyo"]); + Assert.IsFalse(snapshot.Metadata.ContainsKey("Marco")); + + // Test the server view against the expected metadata + snapshot.FetchAttributesAsync().Wait(); + Assert.AreEqual("Dolly", snapshot.Metadata["Hello"]); + Assert.AreEqual("Ma", snapshot.Metadata["Yoyo"]); + Assert.IsFalse(snapshot.Metadata.ContainsKey("Marco")); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Test conditional access on a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobConditionalAccess() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + blob.FetchAttributes(); + + string currentETag = blob.Properties.ETag; + DateTimeOffset currentModifiedTime = blob.Properties.LastModified.Value; + + // ETag conditional tests + blob.Metadata["ETagConditionalName"] = "ETagConditionalValue"; + blob.SetMetadata(AccessCondition.GenerateIfMatchCondition(currentETag), null); + + blob.FetchAttributes(); + string newETag = blob.Properties.ETag; + Assert.AreNotEqual(newETag, currentETag, "ETag should be modified on write metadata"); + + blob.Metadata["ETagConditionalName"] = "ETagConditionalValue2"; + + TestHelper.ExpectedException( + () => blob.SetMetadata(AccessCondition.GenerateIfNoneMatchCondition(newETag), null), + "If none match on conditional test should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + string invalidETag = "\"0x10101010\""; + TestHelper.ExpectedException( + () => blob.SetMetadata(AccessCondition.GenerateIfMatchCondition(invalidETag), null), + "Invalid ETag on conditional test should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + currentETag = blob.Properties.ETag; + blob.SetMetadata(AccessCondition.GenerateIfNoneMatchCondition(invalidETag), null); + + blob.FetchAttributes(); + newETag = blob.Properties.ETag; + + // LastModifiedTime tests + currentModifiedTime = blob.Properties.LastModified.Value; + + blob.Metadata["DateConditionalName"] = "DateConditionalValue"; + + TestHelper.ExpectedException( + () => blob.SetMetadata(AccessCondition.GenerateIfModifiedSinceCondition(currentModifiedTime), null), + "IfModifiedSince conditional on current modified time should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + DateTimeOffset pastTime = currentModifiedTime.Subtract(TimeSpan.FromMinutes(5)); + blob.SetMetadata(AccessCondition.GenerateIfModifiedSinceCondition(pastTime), null); + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromHours(5)); + blob.SetMetadata(AccessCondition.GenerateIfModifiedSinceCondition(pastTime), null); + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromDays(5)); + blob.SetMetadata(AccessCondition.GenerateIfModifiedSinceCondition(pastTime), null); + + currentModifiedTime = blob.Properties.LastModified.Value; + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromMinutes(5)); + TestHelper.ExpectedException( + () => blob.SetMetadata(AccessCondition.GenerateIfNotModifiedSinceCondition(pastTime), null), + "IfNotModifiedSince conditional on past time should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromHours(5)); + TestHelper.ExpectedException( + () => blob.SetMetadata(AccessCondition.GenerateIfNotModifiedSinceCondition(pastTime), null), + "IfNotModifiedSince conditional on past time should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromDays(5)); + TestHelper.ExpectedException( + () => blob.SetMetadata(AccessCondition.GenerateIfNotModifiedSinceCondition(pastTime), null), + "IfNotModifiedSince conditional on past time should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + blob.Metadata["DateConditionalName"] = "DateConditionalValue2"; + + currentETag = blob.Properties.ETag; + blob.SetMetadata(AccessCondition.GenerateIfNotModifiedSinceCondition(currentModifiedTime), null); + + blob.FetchAttributes(); + newETag = blob.Properties.ETag; + Assert.AreNotEqual(newETag, currentETag, "ETage should be modified on write metadata"); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Test conditional access on a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobMaxSizeConditionalAccess() + { + CloudBlobContainer container = GetRandomContainerReference(); + + byte[] buffer = GetRandomBuffer(1 * 1024 * 1024); + + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + + AccessCondition condition = AccessCondition.GenerateIfMaxSizeLessThanOrEqualCondition(2 * 1024 * 1024); + using (MemoryStream originalBlob = new MemoryStream(buffer)) + { + blob.AppendBlock(originalBlob, null, condition, null, null); + + // Seek and upload the 1MB again + originalBlob.Seek(0, SeekOrigin.Begin); + blob.AppendBlock(originalBlob, null, condition, null, null); + + // Seek and try to upload the 1MB again. This time it should fail with a Pre-condition failed error. + originalBlob.Seek(0, SeekOrigin.Begin); + TestHelper.ExpectedException( + () => blob.AppendBlock(originalBlob, null, condition, null, null), + "IfMaxSizeLessThanOrEqual conditional should throw", + HttpStatusCode.PreconditionFailed, + "MaxBlobSizeConditionNotMet"); + + originalBlob.Seek(0, SeekOrigin.Begin); + blob.AppendBlock(originalBlob, null, AccessCondition.GenerateIfMaxSizeLessThanOrEqualCondition(3 * 1024 * 1024), null, null); + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Test conditional access on a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobAppendOffsetConditionalAccess() + { + CloudBlobContainer container = GetRandomContainerReference(); + + byte[] buffer = GetRandomBuffer(1 * 1024 * 1024); + + try + { + container.Create(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + + using (MemoryStream originalBlob = new MemoryStream(buffer)) + { + blob.AppendBlock(originalBlob, null, AccessCondition.GenerateIfAppendPositionEqualCondition(0), null, null); + + // Seek and upload the 1MB again + originalBlob.Seek(0, SeekOrigin.Begin); + blob.AppendBlock(originalBlob, null, AccessCondition.GenerateIfAppendPositionEqualCondition(1 * 1024 * 1024), null, null); + + // Seek and upload the 1MB again. This time it should throw since the append offset does not match + originalBlob.Seek(0, SeekOrigin.Begin); + TestHelper.ExpectedException( + () => blob.AppendBlock(originalBlob, null, AccessCondition.GenerateIfAppendPositionEqualCondition(1 * 1024 * 1024), null, null), + "IfAppendPositionEqual conditional should throw", + HttpStatusCode.PreconditionFailed, + "AppendPositionConditionNotMet"); + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Test append blob methods on a block blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobMethodsOnBlockBlob() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + List blobs = CreateBlobs(container, 1, BlobType.BlockBlob); + CloudAppendBlob blob = container.GetAppendBlobReference(blobs.First()); + + using (MemoryStream stream = new MemoryStream()) + { + stream.SetLength(512); + TestHelper.ExpectedException( + () => blob.AppendBlock(stream, null), + "Append operations should fail on block blobs", + HttpStatusCode.Conflict, + "InvalidBlobType"); + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Test block blob methods on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudBlockBlobMethodsOnAppendBlob() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + List blocks = GetBlockIdList(1); + List blobs = CreateBlobs(container, 1, BlobType.AppendBlob); + CloudBlockBlob blob = container.GetBlockBlobReference(blobs.First()); + + TestHelper.ExpectedException( + () => blob.PutBlockList(blocks), + "Block operations should fail on append blobs", + HttpStatusCode.Conflict, + "InvalidBlobType"); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Test page blob methods on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudPageBlobMethodsOnAppendBlob() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + List blobs = CreateBlobs(container, 1, BlobType.AppendBlob); + CloudPageBlob blob = container.GetPageBlobReference(blobs.First()); + + using (MemoryStream stream = new MemoryStream()) + { + stream.SetLength(512); + TestHelper.ExpectedException( + () => blob.WritePages(stream, 0), + "Page operations should fail on append blobs", + HttpStatusCode.Conflict, + "InvalidBlobType"); + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Try operations with an invalid Sas and snapshot")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobInvalidSasAndSnapshot() + { + // Sas token creds. + string token = "?sp=abcde&sig=1"; + StorageCredentials creds = new StorageCredentials(token); + Assert.IsTrue(creds.IsSAS); + + // Client with shared key access. + CloudBlobClient blobClient = GenerateCloudBlobClient(); + CloudBlobContainer container = blobClient.GetContainerReference(GetRandomContainerName()); + try + { + container.Create(); + + SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Write, + }; + string sasToken = container.GetSharedAccessSignature(policy); + + string blobUri = container.Uri.AbsoluteUri + "/blob1" + sasToken; + TestHelper.ExpectedException( + () => new CloudAppendBlob(new Uri(blobUri), container.ServiceClient.Credentials), + "Try to use SAS creds in Uri on a shared key client"); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.CreateOrReplace(); + CloudAppendBlob snapshot = blob.CreateSnapshot(); + DateTimeOffset? wrongTime = snapshot.SnapshotTime.Value + TimeSpan.FromSeconds(10); + + string snapshotUri = snapshot.Uri + "?snapshot=" + wrongTime.Value.ToString(); + TestHelper.ExpectedException( + () => new CloudAppendBlob(new Uri(snapshotUri), snapshot.SnapshotTime, container.ServiceClient.Credentials), + "Snapshot in Uri does not match snapshot on blob"); + + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Use IASyncResult's WaitHandle")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void IAsyncWaitHandleTest() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + container.Create(); + + IAsyncResult result; + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + result = blob.BeginCreateOrReplace(null, null); + result.AsyncWaitHandle.WaitOne(); + blob.EndCreateOrReplace(result); + + result = blob.BeginExists(null, null); + result.AsyncWaitHandle.WaitOne(); + Assert.IsTrue(blob.EndExists(result)); + + result = blob.BeginDelete(null, null); + result.AsyncWaitHandle.WaitOne(); + blob.EndDelete(result); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Generate SAS for Snapshots")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobGenerateSASForSnapshot() + { + // Client with shared key access. + CloudBlobClient blobClient = GenerateCloudBlobClient(); + CloudBlobContainer container = blobClient.GetContainerReference(GetRandomContainerName()); + MemoryStream memoryStream = new MemoryStream(); + try + { + container.Create(); + CloudAppendBlob blob = container.GetAppendBlobReference("Testing"); + blob.CreateOrReplace(); + SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Write, + }; + + CloudAppendBlob snapshot = blob.CreateSnapshot(); + string sas = snapshot.GetSharedAccessSignature(policy); + Assert.IsNotNull(sas); + StorageCredentials credentials = new StorageCredentials(sas); + Uri snapshotUri = snapshot.SnapshotQualifiedUri; + CloudAppendBlob blob1 = new CloudAppendBlob(snapshotUri, credentials); + blob1.DownloadToStream(memoryStream); + Assert.IsNotNull(memoryStream); + } + finally + { + container.DeleteIfExists(); + memoryStream.Close(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadFromStream() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.Create(); + try + { + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, null, null, true, true, 0, false, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, null, null, true, true, 1024, false, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, null, null, true, false, 0, false, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, null, null, true, false, 1024, false, true); + } + finally + { + container.Delete(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadFromStreamAPM() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.Create(); + try + { + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, null, null, true, true, 0, true, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, null, null, true, true, 1024, true, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, null, null, true, false, 0, true, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, null, null, true, false, 1024, true, true); + } + finally + { + container.Delete(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadFromStreamLength() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.Create(); + try + { + // Upload 2MB of a 5MB stream + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, true, true, 0, false, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, true, true, 1024, false, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, true, false, 0, false, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, true, false, 1024, false, true); + + // Exclude last byte + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 5 * 1024 * 1024 - 1, null, true, true, 0, false, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 4 * 1024 * 1024 - 1, null, true, true, 1024, false, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 5 * 1024 * 1024 - 1, null, true, false, 0, false, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 4 * 1024 * 1024 - 1, null, true, false, 1024, false, true); + + // Upload exact amount + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 5 * 1024 * 1024, null, true, true, 0, false, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 4 * 1024 * 1024, null, true, true, 1024, false, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 5 * 1024 * 1024, null, true, false, 0, false, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 4 * 1024 * 1024, null, true, false, 1024, false, true); + } + finally + { + container.Delete(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadFromStreamLengthAPM() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.Create(); + try + { + // Upload 2MB of a 5MB stream + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, true, true, 0, true, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, true, true, 1024, true, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, true, false, 0, true, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, true, false, 1024, true, true); + + // Exclude last byte + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 5 * 1024 * 1024 - 1, null, true, true, 0, true, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 4 * 1024 * 1024 - 1, null, true, true, 1024, true, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 5 * 1024 * 1024 - 1, null, true, false, 0, true, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 4 * 1024 * 1024 - 1, null, true, false, 1024, true, true); + + // Upload exact amount + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 5 * 1024 * 1024, null, true, true, 0, true, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 4 * 1024 * 1024, null, true, true, 1024, true, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 5 * 1024 * 1024, null, true, false, 0, true, true); + this.CloudAppendBlobUploadFromStream(container, 5 * 1024 * 1024, 4 * 1024 * 1024, null, true, false, 1024, true, true); + } + finally + { + container.Delete(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadFromStreamLengthInvalid() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.Create(); + try + { + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 + 1, null, true, true, 0, false, false), + "The given stream does not contain the requested number of bytes from its given position."); + + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 - 1023, null, true, true, 1024, false, false), + "The given stream does not contain the requested number of bytes from its given position."); + + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 + 1, null, false, true, 0, false, false), + "The given stream does not contain the requested number of bytes from its given position."); + + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 - 1023, null, false, true, 1024, false, false), + "The given stream does not contain the requested number of bytes from its given position."); + + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 + 1, null, true, false, 0, false, false), + "The given stream does not contain the requested number of bytes from its given position."); + + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 - 1023, null, true, false, 1024, false, false), + "The given stream does not contain the requested number of bytes from its given position."); + + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 + 1, null, false, false, 0, false, false), + "The given stream does not contain the requested number of bytes from its given position."); + + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 - 1023, null, false, false, 1024, false, false), + "The given stream does not contain the requested number of bytes from its given position."); + } + finally + { + container.Delete(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobUploadFromStreamLengthInvalidAPM() + { + CloudBlobContainer container = GetRandomContainerReference(); + container.Create(); + try + { + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 + 1, null, true, true, 0, true, false), + "The given stream does not contain the requested number of bytes from its given position."); + + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 - 1023, null, true, true, 1024, true, false), + "The given stream does not contain the requested number of bytes from its given position."); + + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 + 1, null, false, true, 0, true, false), + "The given stream does not contain the requested number of bytes from its given position."); + + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 - 1023, null, false, true, 1024, true, false), + "The given stream does not contain the requested number of bytes from its given position."); + + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 + 1, null, true, false, 0, true, false), + "The given stream does not contain the requested number of bytes from its given position."); + + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 - 1023, null, true, false, 1024, true, false), + "The given stream does not contain the requested number of bytes from its given position."); + + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 + 1, null, false, false, 0, true, false), + "The given stream does not contain the requested number of bytes from its given position."); + + TestHelper.ExpectedException( + () => + this.CloudAppendBlobUploadFromStream( + container, 2 * 1024 * 1024, 2 * 1024 * 1024 - 1023, null, false, false, 1024, true, false), + "The given stream does not contain the requested number of bytes from its given position."); + } + finally + { + container.Delete(); + } + } + + private void CloudAppendBlobUploadFromStream(CloudBlobContainer container, int size, long? copyLength, AccessCondition accessCondition, bool seekableSourceStream, bool allowSinglePut, int startOffset, bool isAsync, bool testMd5) + { + byte[] buffer = GetRandomBuffer(size); + + MD5 hasher = MD5.Create(); + + string md5 = string.Empty; + if (testMd5) + { + md5 = Convert.ToBase64String(hasher.ComputeHash(buffer, startOffset, copyLength.HasValue ? (int)copyLength : buffer.Length - startOffset)); + } + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamWriteSizeInBytes = 1 * 1024 * 1024; + + using (MemoryStream originalBlobStream = new MemoryStream()) + { + originalBlobStream.Write(buffer, startOffset, buffer.Length - startOffset); + + Stream sourceStream; + if (seekableSourceStream) + { + MemoryStream stream = new MemoryStream(buffer); + stream.Seek(startOffset, SeekOrigin.Begin); + sourceStream = stream; + } + else + { + NonSeekableMemoryStream stream = new NonSeekableMemoryStream(buffer); + stream.ForceSeek(startOffset, SeekOrigin.Begin); + sourceStream = stream; + } + + using (sourceStream) + { + BlobRequestOptions options = new BlobRequestOptions() + { + StoreBlobContentMD5 = true, + }; + if (isAsync) + { + using (ManualResetEvent waitHandle = new ManualResetEvent(false)) + { + if (copyLength.HasValue) + { + ICancellableAsyncResult result = blob.BeginUploadFromStream( + sourceStream, copyLength.Value, accessCondition, options, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndUploadFromStream(result); + } + else + { + ICancellableAsyncResult result = blob.BeginUploadFromStream( + sourceStream, accessCondition, options, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndUploadFromStream(result); + } + } + } + else + { + if (copyLength.HasValue) + { + blob.UploadFromStream(sourceStream, copyLength.Value, accessCondition, options); + } + else + { + blob.UploadFromStream(sourceStream, accessCondition, options); + } + } + } + + blob.FetchAttributes(); + + if (testMd5) + { + Assert.AreEqual(md5, blob.Properties.ContentMD5); + } + + using (MemoryStream downloadedBlobStream = new MemoryStream()) + { + if (isAsync) + { + using (ManualResetEvent waitHandle = new ManualResetEvent(false)) + { + ICancellableAsyncResult result = blob.BeginDownloadToStream( + downloadedBlobStream, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + blob.EndDownloadToStream(result); + } + } + else + { + blob.DownloadToStream(downloadedBlobStream); + } + + Assert.AreEqual(copyLength ?? originalBlobStream.Length, downloadedBlobStream.Length); + TestHelper.AssertStreamsAreEqualAtIndex( + originalBlobStream, + downloadedBlobStream, + 0, + 0, + copyLength.HasValue ? (int)copyLength : (int)originalBlobStream.Length); + } + } + + blob.Delete(); + } + } +} \ No newline at end of file diff --git a/Test/ClassLibraryCommon/Blob/CloudBlobClientTest.cs b/Test/ClassLibraryCommon/Blob/CloudBlobClientTest.cs index 697017dde..7c6d8533a 100644 --- a/Test/ClassLibraryCommon/Blob/CloudBlobClientTest.cs +++ b/Test/ClassLibraryCommon/Blob/CloudBlobClientTest.cs @@ -96,6 +96,8 @@ public void CloudBlobClientObjects() Assert.AreEqual(blobClient, blockBlob.ServiceClient); CloudPageBlob pageBlob = container.GetPageBlobReference("pageblob"); Assert.AreEqual(blobClient, pageBlob.ServiceClient); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("appendblob"); + Assert.AreEqual(blobClient, appendBlob.ServiceClient); CloudBlobContainer container2 = GetRandomContainerReference(); Assert.AreNotEqual(blobClient, container2.ServiceClient); @@ -103,6 +105,8 @@ public void CloudBlobClientObjects() Assert.AreEqual(container2.ServiceClient, blockBlob2.ServiceClient); CloudPageBlob pageBlob2 = container2.GetPageBlobReference("pageblob"); Assert.AreEqual(container2.ServiceClient, pageBlob2.ServiceClient); + CloudAppendBlob appendBlob2 = container2.GetAppendBlobReference("appendblob"); + Assert.AreEqual(container2.ServiceClient, appendBlob2.ServiceClient); } [TestMethod] diff --git a/Test/ClassLibraryCommon/Blob/CloudBlobContainerTest.cs b/Test/ClassLibraryCommon/Blob/CloudBlobContainerTest.cs index fcd587f53..0963e119a 100644 --- a/Test/ClassLibraryCommon/Blob/CloudBlobContainerTest.cs +++ b/Test/ClassLibraryCommon/Blob/CloudBlobContainerTest.cs @@ -34,7 +34,7 @@ namespace Microsoft.WindowsAzure.Storage.Blob [TestClass] public class CloudBlobContainerTest : BlobTestBase { - private static void TestAccess(BlobContainerPublicAccessType accessType, CloudBlobContainer container, ICloudBlob inputBlob) + private static void TestAccess(BlobContainerPublicAccessType accessType, CloudBlobContainer container, CloudBlob inputBlob) { StorageCredentials credentials = new StorageCredentials(); container = new CloudBlobContainer(container.Uri, credentials); @@ -76,7 +76,7 @@ private static void TestAccess(BlobContainerPublicAccessType accessType, CloudBl } #if TASK - private static void TestAccessTask(BlobContainerPublicAccessType accessType, CloudBlobContainer container, ICloudBlob inputBlob) + private static void TestAccessTask(BlobContainerPublicAccessType accessType, CloudBlobContainer container, CloudBlob inputBlob) { StorageCredentials credentials = new StorageCredentials(); container = new CloudBlobContainer(container.Uri, credentials); @@ -197,11 +197,13 @@ public void CloudBlobContainerReference() CloudBlobContainer container = client.GetContainerReference("container"); CloudBlockBlob blockBlob = container.GetBlockBlobReference("directory1/blob1"); CloudPageBlob pageBlob = container.GetPageBlobReference("directory2/blob2"); - CloudBlobDirectory directory = container.GetDirectoryReference("directory3"); - CloudBlobDirectory directory2 = directory.GetDirectoryReference("directory4"); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("directory3/blob3"); + CloudBlobDirectory directory = container.GetDirectoryReference("directory4"); + CloudBlobDirectory directory2 = directory.GetDirectoryReference("directory5"); Assert.AreEqual(container, blockBlob.Container); Assert.AreEqual(container, pageBlob.Container); + Assert.AreEqual(container, appendBlob.Container); Assert.AreEqual(container, directory.Container); Assert.AreEqual(container, directory2.Container); Assert.AreEqual(container, directory2.Parent.Container); @@ -2095,16 +2097,17 @@ public void CloudBlobContainerListManyBlobs() try { container.Create(); - List pageBlobNames = CreateBlobs(container, 3000, BlobType.PageBlob); - List blockBlobNames = CreateBlobs(container, 3000, BlobType.BlockBlob); + List pageBlobNames = CreateBlobs(container, 2000, BlobType.PageBlob); + List blockBlobNames = CreateBlobs(container, 2000, BlobType.BlockBlob); + List appendBlobNames = CreateBlobs(container, 2000, BlobType.AppendBlob); int count = 0; IEnumerable results = container.ListBlobs(); foreach (IListBlobItem blobItem in results) { count++; - Assert.IsInstanceOfType(blobItem, typeof(ICloudBlob)); - ICloudBlob blob = (ICloudBlob)blobItem; + Assert.IsInstanceOfType(blobItem, typeof(CloudBlob)); + CloudBlob blob = (CloudBlob)blobItem; if (pageBlobNames.Remove(blob.Name)) { Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); @@ -2113,6 +2116,10 @@ public void CloudBlobContainerListManyBlobs() { Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); } + else if (appendBlobNames.Remove(blob.Name)) + { + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + } else { Assert.Fail("Unexpected blob: " + blob.Uri.AbsoluteUri); @@ -2421,7 +2428,7 @@ public void CloudBlobContainerListBlobsWithSecondaryUri() do { BlobResultSegment results = container.ListBlobsSegmented(null, true, BlobListingDetails.None, 1, token, null, null); - foreach (ICloudBlob blob in results.Results) + foreach (CloudBlob blob in results.Results) { Assert.IsTrue(blobNames.Remove(blob.Name)); Assert.IsTrue(container.GetBlockBlobReference(blob.Name).StorageUri.Equals(blob.StorageUri)); @@ -2649,9 +2656,12 @@ public void CloudBlobContainerGetBlobReferenceFromServer() CloudPageBlob pageBlob = container.GetPageBlobReference("pb"); pageBlob.Create(0); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("ab"); + appendBlob.CreateOrReplace(); + CloudBlobClient client; ICloudBlob blob; - + blob = container.GetBlobReferenceFromServer("bb"); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.Equals(blockBlob.StorageUri)); @@ -2676,6 +2686,18 @@ public void CloudBlobContainerGetBlobReferenceFromServer() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlobSnapshot.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = container.GetBlobReferenceFromServer("ab"); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + + CloudAppendBlob appendBlobSnapshot = ((CloudAppendBlob)blob).CreateSnapshot(); + blob.SetProperties(); + Uri appendBlobSnapshotUri = new Uri(appendBlobSnapshot.Uri.AbsoluteUri + "?snapshot=" + appendBlobSnapshot.SnapshotTime.Value.UtcDateTime.ToString("o")); + blob = container.ServiceClient.GetBlobReferenceFromServer(appendBlobSnapshotUri); + AssertAreEqual(appendBlobSnapshot.Properties, blob.Properties); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlobSnapshot.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = container.ServiceClient.GetBlobReferenceFromServer(blockBlob.Uri); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(blockBlob.Uri)); @@ -2686,6 +2708,11 @@ public void CloudBlobContainerGetBlobReferenceFromServer() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = container.ServiceClient.GetBlobReferenceFromServer(appendBlob.Uri); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = container.ServiceClient.GetBlobReferenceFromServer(blockBlob.StorageUri); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.Equals(blockBlob.StorageUri)); @@ -2694,6 +2721,10 @@ public void CloudBlobContainerGetBlobReferenceFromServer() Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + blob = container.ServiceClient.GetBlobReferenceFromServer(appendBlob.StorageUri); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + string blockBlobToken = blockBlob.GetSharedAccessSignature(policy); StorageCredentials blockBlobSAS = new StorageCredentials(blockBlobToken); Uri blockBlobSASUri = blockBlobSAS.TransformUri(blockBlob.Uri); @@ -2704,6 +2735,11 @@ public void CloudBlobContainerGetBlobReferenceFromServer() Uri pageBlobSASUri = pageBlobSAS.TransformUri(pageBlob.Uri); StorageUri pageBlobSASStorageUri = pageBlobSAS.TransformUri(pageBlob.StorageUri); + string appendBlobToken = appendBlob.GetSharedAccessSignature(policy); + StorageCredentials appendBlobSAS = new StorageCredentials(appendBlobToken); + Uri appendBlobSASUri = appendBlobSAS.TransformUri(appendBlob.Uri); + StorageUri appendBlobSASStorageUri = appendBlobSAS.TransformUri(appendBlob.StorageUri); + blob = container.ServiceClient.GetBlobReferenceFromServer(blockBlobSASUri); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(blockBlob.Uri)); @@ -2714,6 +2750,11 @@ public void CloudBlobContainerGetBlobReferenceFromServer() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = container.ServiceClient.GetBlobReferenceFromServer(appendBlobSASUri); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = container.ServiceClient.GetBlobReferenceFromServer(blockBlobSASStorageUri); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.Equals(blockBlob.StorageUri)); @@ -2722,6 +2763,10 @@ public void CloudBlobContainerGetBlobReferenceFromServer() Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + blob = container.ServiceClient.GetBlobReferenceFromServer(appendBlobSASStorageUri); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + client = new CloudBlobClient(container.ServiceClient.BaseUri, blockBlobSAS); blob = client.GetBlobReferenceFromServer(blockBlobSASUri); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); @@ -2734,6 +2779,12 @@ public void CloudBlobContainerGetBlobReferenceFromServer() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + client = new CloudBlobClient(container.ServiceClient.BaseUri, appendBlobSAS); + blob = client.GetBlobReferenceFromServer(appendBlobSASUri); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + client = new CloudBlobClient(container.ServiceClient.StorageUri, blockBlobSAS); blob = client.GetBlobReferenceFromServer(blockBlobSASStorageUri); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); @@ -2743,6 +2794,11 @@ public void CloudBlobContainerGetBlobReferenceFromServer() blob = client.GetBlobReferenceFromServer(pageBlobSASStorageUri); Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + + client = new CloudBlobClient(container.ServiceClient.StorageUri, appendBlobSAS); + blob = client.GetBlobReferenceFromServer(appendBlobSASStorageUri); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); } finally { @@ -2776,12 +2832,15 @@ public void CloudBlobContainerGetBlobReferenceFromServerAPM() CloudPageBlob pageBlob = container.GetPageBlobReference("pb"); pageBlob.Create(0); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("ab"); + appendBlob.CreateOrReplace(); + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) { CloudBlobClient client; ICloudBlob blob; IAsyncResult result; - + result = container.BeginGetBlobReferenceFromServer("bb", ar => waitHandle.Set(), null); @@ -2822,6 +2881,26 @@ public void CloudBlobContainerGetBlobReferenceFromServerAPM() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlobSnapshot.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + result = container.BeginGetBlobReferenceFromServer("ab", + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob = container.EndGetBlobReferenceFromServer(result); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + + CloudAppendBlob appendBlobSnapshot = ((CloudAppendBlob)blob).CreateSnapshot(); + blob.SetProperties(); + Uri appendBlobSnapshotUri = new Uri(appendBlobSnapshot.Uri.AbsoluteUri + "?snapshot=" + appendBlobSnapshot.SnapshotTime.Value.UtcDateTime.ToString("o")); + result = container.ServiceClient.BeginGetBlobReferenceFromServer(appendBlobSnapshotUri, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob = container.EndGetBlobReferenceFromServer(result); + AssertAreEqual(appendBlobSnapshot.Properties, blob.Properties); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlobSnapshot.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + result = container.ServiceClient.BeginGetBlobReferenceFromServer(blockBlob.Uri, ar => waitHandle.Set(), null); @@ -2840,6 +2919,15 @@ public void CloudBlobContainerGetBlobReferenceFromServerAPM() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + result = container.ServiceClient.BeginGetBlobReferenceFromServer(appendBlob.Uri, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob = container.EndGetBlobReferenceFromServer(result); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + result = container.ServiceClient.BeginGetBlobReferenceFromServer(blockBlob.StorageUri, null, null, null, ar => waitHandle.Set(), null); @@ -2856,6 +2944,14 @@ public void CloudBlobContainerGetBlobReferenceFromServerAPM() Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + result = container.ServiceClient.BeginGetBlobReferenceFromServer(appendBlob.StorageUri, null, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob = container.EndGetBlobReferenceFromServer(result); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + string blockBlobToken = blockBlob.GetSharedAccessSignature(policy); StorageCredentials blockBlobSAS = new StorageCredentials(blockBlobToken); Uri blockBlobSASUri = blockBlobSAS.TransformUri(blockBlob.Uri); @@ -2866,6 +2962,11 @@ public void CloudBlobContainerGetBlobReferenceFromServerAPM() Uri pageBlobSASUri = pageBlobSAS.TransformUri(pageBlob.Uri); StorageUri pageBlobSASStorageUri = pageBlobSAS.TransformUri(pageBlob.StorageUri); + string appendBlobToken = appendBlob.GetSharedAccessSignature(policy); + StorageCredentials appendBlobSAS = new StorageCredentials(appendBlobToken); + Uri appendBlobSASUri = appendBlobSAS.TransformUri(appendBlob.Uri); + StorageUri appendBlobSASStorageUri = appendBlobSAS.TransformUri(appendBlob.StorageUri); + result = container.ServiceClient.BeginGetBlobReferenceFromServer(blockBlobSASUri, ar => waitHandle.Set(), null); @@ -2884,6 +2985,15 @@ public void CloudBlobContainerGetBlobReferenceFromServerAPM() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + result = container.ServiceClient.BeginGetBlobReferenceFromServer(appendBlobSASUri, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob = container.EndGetBlobReferenceFromServer(result); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + result = container.ServiceClient.BeginGetBlobReferenceFromServer(blockBlobSASStorageUri, null, null, null, ar => waitHandle.Set(), null); @@ -2900,6 +3010,14 @@ public void CloudBlobContainerGetBlobReferenceFromServerAPM() Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + result = container.ServiceClient.BeginGetBlobReferenceFromServer(appendBlobSASStorageUri, null, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob = container.EndGetBlobReferenceFromServer(result); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + client = new CloudBlobClient(container.ServiceClient.BaseUri, blockBlobSAS); result = container.ServiceClient.BeginGetBlobReferenceFromServer(blockBlobSASUri, ar => waitHandle.Set(), @@ -2920,6 +3038,16 @@ public void CloudBlobContainerGetBlobReferenceFromServerAPM() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + client = new CloudBlobClient(container.ServiceClient.BaseUri, appendBlobSAS); + result = container.ServiceClient.BeginGetBlobReferenceFromServer(appendBlobSASUri, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob = container.EndGetBlobReferenceFromServer(result); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + client = new CloudBlobClient(container.ServiceClient.StorageUri, blockBlobSAS); result = container.ServiceClient.BeginGetBlobReferenceFromServer(blockBlobSASStorageUri, null, null, null, ar => waitHandle.Set(), @@ -2937,6 +3065,15 @@ public void CloudBlobContainerGetBlobReferenceFromServerAPM() blob = container.EndGetBlobReferenceFromServer(result); Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + + client = new CloudBlobClient(container.ServiceClient.StorageUri, appendBlobSAS); + result = container.ServiceClient.BeginGetBlobReferenceFromServer(appendBlobSASStorageUri, null, null, null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob = container.EndGetBlobReferenceFromServer(result); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); } } finally @@ -2972,6 +3109,9 @@ public void CloudBlobContainerGetBlobReferenceFromServerTask() CloudPageBlob pageBlob = container.GetPageBlobReference("pb"); pageBlob.Create(0); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("ab"); + appendBlob.CreateOrReplace(); + CloudBlobClient client; ICloudBlob blob; @@ -2999,6 +3139,18 @@ public void CloudBlobContainerGetBlobReferenceFromServerTask() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlobSnapshot.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = container.GetBlobReferenceFromServerAsync("ab").Result; + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + + CloudAppendBlob appendBlobSnapshot = ((CloudAppendBlob)blob).CreateSnapshot(); + blob.SetProperties(); + Uri appendBlobSnapshotUri = new Uri(appendBlobSnapshot.Uri.AbsoluteUri + "?snapshot=" + appendBlobSnapshot.SnapshotTime.Value.UtcDateTime.ToString("o")); + blob = container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlobSnapshotUri).Result; + AssertAreEqual(appendBlobSnapshot.Properties, blob.Properties); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlobSnapshot.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = container.ServiceClient.GetBlobReferenceFromServerAsync(blockBlob.Uri).Result; Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(blockBlob.Uri)); @@ -3009,6 +3161,11 @@ public void CloudBlobContainerGetBlobReferenceFromServerTask() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlob.Uri).Result; + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = container.ServiceClient.GetBlobReferenceFromServerAsync(blockBlob.StorageUri, null, null, null).Result; Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.Equals(blockBlob.StorageUri)); @@ -3017,6 +3174,10 @@ public void CloudBlobContainerGetBlobReferenceFromServerTask() Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + blob = container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlob.StorageUri, null, null, null).Result; + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + string blockBlobToken = blockBlob.GetSharedAccessSignature(policy); StorageCredentials blockBlobSAS = new StorageCredentials(blockBlobToken); Uri blockBlobSASUri = blockBlobSAS.TransformUri(blockBlob.Uri); @@ -3027,6 +3188,11 @@ public void CloudBlobContainerGetBlobReferenceFromServerTask() Uri pageBlobSASUri = pageBlobSAS.TransformUri(pageBlob.Uri); StorageUri pageBlobSASStorageUri = pageBlobSAS.TransformUri(pageBlob.StorageUri); + string appendBlobToken = appendBlob.GetSharedAccessSignature(policy); + StorageCredentials appendBlobSAS = new StorageCredentials(appendBlobToken); + Uri appendBlobSASUri = appendBlobSAS.TransformUri(appendBlob.Uri); + StorageUri appendBlobSASStorageUri = appendBlobSAS.TransformUri(appendBlob.StorageUri); + blob = container.ServiceClient.GetBlobReferenceFromServerAsync(blockBlobSASUri).Result; Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(blockBlob.Uri)); @@ -3037,6 +3203,11 @@ public void CloudBlobContainerGetBlobReferenceFromServerTask() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlobSASUri).Result; + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = container.ServiceClient.GetBlobReferenceFromServerAsync(blockBlobSASStorageUri, null, null, null).Result; Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.Equals(blockBlob.StorageUri)); @@ -3045,6 +3216,10 @@ public void CloudBlobContainerGetBlobReferenceFromServerTask() Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + blob = container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlobSASStorageUri, null, null, null).Result; + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + client = new CloudBlobClient(container.ServiceClient.BaseUri, blockBlobSAS); blob = client.GetBlobReferenceFromServerAsync(blockBlobSASUri).Result; Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); @@ -3057,6 +3232,12 @@ public void CloudBlobContainerGetBlobReferenceFromServerTask() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + client = new CloudBlobClient(container.ServiceClient.BaseUri, appendBlobSAS); + blob = client.GetBlobReferenceFromServerAsync(appendBlobSASUri).Result; + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + client = new CloudBlobClient(container.ServiceClient.StorageUri, blockBlobSAS); blob = client.GetBlobReferenceFromServerAsync(blockBlobSASStorageUri, null, null, null).Result; Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); @@ -3066,6 +3247,11 @@ public void CloudBlobContainerGetBlobReferenceFromServerTask() blob = client.GetBlobReferenceFromServerAsync(pageBlobSASStorageUri, null, null, null).Result; Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + + client = new CloudBlobClient(container.ServiceClient.StorageUri, appendBlobSAS); + blob = client.GetBlobReferenceFromServerAsync(appendBlobSASStorageUri, null, null, null).Result; + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); } finally { diff --git a/Test/ClassLibraryCommon/Blob/CloudBlobTest.cs b/Test/ClassLibraryCommon/Blob/CloudBlobTest.cs index a7c33fe9c..9a279840e 100644 --- a/Test/ClassLibraryCommon/Blob/CloudBlobTest.cs +++ b/Test/ClassLibraryCommon/Blob/CloudBlobTest.cs @@ -65,8 +65,9 @@ public void CloudBlobSnapshot() // Upload some data to the blob. MemoryStream originalData = new MemoryStream(GetRandomBuffer(1024)); - CloudBlockBlob blockBlob = container.GetBlockBlobReference(BlobName); - blockBlob.UploadFromStream(originalData); + CloudAppendBlob appendBlob = container.GetAppendBlobReference(BlobName); + appendBlob.CreateOrReplace(); + appendBlob.AppendBlock(originalData, null); CloudBlob blob = container.GetBlobReference(BlobName); blob.FetchAttributes(); @@ -110,6 +111,7 @@ public void CloudBlobSnapshot() TestHelper.AssertStreamsAreEqual(originalData, snapshotStream); } + appendBlob.CreateOrReplace(); blob.FetchAttributes(); using (Stream snapshotStream = snapshot1.OpenRead()) @@ -145,8 +147,9 @@ public void CloudBlobSnapshotAPM() container.Create(); MemoryStream originalData = new MemoryStream(GetRandomBuffer(1024)); - CloudBlockBlob blockBlob = container.GetBlockBlobReference(BlobName); - blockBlob.UploadFromStream(originalData); + CloudAppendBlob appendBlob = container.GetAppendBlobReference(BlobName); + appendBlob.CreateOrReplace(); + appendBlob.AppendBlock(originalData, null); CloudBlob blob = container.GetBlobReference(BlobName); blob.FetchAttributes(); @@ -190,6 +193,9 @@ public void CloudBlobSnapshotAPM() TestHelper.AssertStreamsAreEqual(originalData, snapshotStream); } + result = appendBlob.BeginCreateOrReplace(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + appendBlob.EndCreateOrReplace(result); result = blob.BeginFetchAttributes(ar => waitHandle.Set(), null); waitHandle.WaitOne(); blob.EndFetchAttributes(result); @@ -231,8 +237,9 @@ public void CloudBlobSnapshotTask() container.CreateAsync().Wait(); MemoryStream originalData = new MemoryStream(GetRandomBuffer(1024)); - CloudBlockBlob blockBlob = container.GetBlockBlobReference(BlobName); - blockBlob.UploadFromStream(originalData); + CloudAppendBlob appendBlob = container.GetAppendBlobReference(BlobName); + appendBlob.CreateOrReplaceAsync().Wait(); + appendBlob.AppendBlockAsync(originalData, null).Wait(); CloudBlob blob = container.GetBlobReference(BlobName); blob.FetchAttributesAsync().Wait(); @@ -271,6 +278,7 @@ public void CloudBlobSnapshotTask() TestHelper.AssertStreamsAreEqual(originalData, snapshotStream); } + appendBlob.CreateOrReplaceAsync().Wait(); blob.FetchAttributesAsync().Wait(); using (Stream snapshotStream = snapshot1.OpenReadAsync().Result) @@ -310,8 +318,8 @@ public void CloudBlobSnapshotMetadata() { container.Create(); - CloudPageBlob pageBlob = container.GetPageBlobReference(BlobName); - pageBlob.Create(512); + CloudAppendBlob appendBlob = container.GetAppendBlobReference(BlobName); + appendBlob.CreateOrReplace(); CloudBlob blob = container.GetBlobReference(BlobName); blob.Metadata["Hello"] = "World"; @@ -355,8 +363,8 @@ public void CloudBlobSnapshotMetadataAPM() { container.Create(); - CloudPageBlob pageBlob = container.GetPageBlobReference(BlobName); - pageBlob.Create(512); + CloudAppendBlob appendBlob = container.GetAppendBlobReference(BlobName); + appendBlob.CreateOrReplace(); CloudBlob blob = container.GetBlobReference(BlobName); blob.Metadata["Hello"] = "World"; @@ -408,8 +416,8 @@ public void CloudBlobSnapshotMetadataTask() { container.CreateAsync().Wait(); - CloudPageBlob pageBlob = container.GetPageBlobReference(BlobName); - pageBlob.Create(512); + CloudAppendBlob appendBlob = container.GetAppendBlobReference(BlobName); + appendBlob.CreateOrReplaceAsync(null, null, new OperationContext()).Wait(); CloudBlob blob = container.GetBlobReference(BlobName); blob.Metadata["Hello"] = "World"; diff --git a/Test/ClassLibraryCommon/Blob/CloudBlockBlobTest.cs b/Test/ClassLibraryCommon/Blob/CloudBlockBlobTest.cs index 40a4b841a..2ed8c0222 100644 --- a/Test/ClassLibraryCommon/Blob/CloudBlockBlobTest.cs +++ b/Test/ClassLibraryCommon/Blob/CloudBlockBlobTest.cs @@ -2494,7 +2494,7 @@ public void CloudBlockBlobSnapshot() AssertAreEqual(snapshot1.Properties, snapshot1Clone.Properties); CloudBlockBlob snapshotCopy = container.GetBlockBlobReference("blob2"); - snapshotCopy.StartCopyFromBlob(TestHelper.Defiddler(snapshot1.Uri)); + snapshotCopy.StartCopy(TestHelper.Defiddler(snapshot1.Uri)); WaitForCopy(snapshotCopy); Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); @@ -2579,9 +2579,9 @@ public void CloudBlockBlobSnapshotAPM() AssertAreEqual(snapshot1.Properties, blob.Properties); CloudBlockBlob snapshotCopy = container.GetBlockBlobReference("blob2"); - result = snapshotCopy.BeginStartCopyFromBlob(snapshot1, null, null, null, null, ar => waitHandle.Set(), null); + result = snapshotCopy.BeginStartCopy(snapshot1, null, null, null, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - snapshotCopy.EndStartCopyFromBlob(result); + snapshotCopy.EndStartCopy(result); WaitForCopy(snapshotCopy); Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); @@ -2673,7 +2673,7 @@ public void CloudBlockBlobSnapshotTask() AssertAreEqual(snapshot1.Properties, blob.Properties); CloudBlockBlob snapshotCopy = container.GetBlockBlobReference("blob2"); - snapshotCopy.StartCopyFromBlobAsync(snapshot1, null, null, null, null).Wait(); + snapshotCopy.StartCopyAsync(snapshot1, null, null, null, null).Wait(); bool copyInProgress = true; while (copyInProgress) { diff --git a/Test/ClassLibraryCommon/Blob/CloudPageBlobTest.cs b/Test/ClassLibraryCommon/Blob/CloudPageBlobTest.cs index 6072802c1..31986f65a 100644 --- a/Test/ClassLibraryCommon/Blob/CloudPageBlobTest.cs +++ b/Test/ClassLibraryCommon/Blob/CloudPageBlobTest.cs @@ -2873,7 +2873,7 @@ public void CloudPageBlobSnapshot() AssertAreEqual(snapshot1.Properties, snapshot1Clone.Properties); CloudPageBlob snapshotCopy = container.GetPageBlobReference("blob2"); - snapshotCopy.StartCopyFromBlob(TestHelper.Defiddler(snapshot1.Uri)); + snapshotCopy.StartCopy(TestHelper.Defiddler(snapshot1.Uri)); WaitForCopy(snapshotCopy); Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); @@ -2957,9 +2957,9 @@ public void CloudPageBlobSnapshotAPM() AssertAreEqual(snapshot1.Properties, blob.Properties); CloudPageBlob snapshotCopy = container.GetPageBlobReference("blob2"); - result = snapshotCopy.BeginStartCopyFromBlob(snapshot1, null, null, null, null, ar => waitHandle.Set(), null); + result = snapshotCopy.BeginStartCopy(snapshot1, null, null, null, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - snapshotCopy.EndStartCopyFromBlob(result); + snapshotCopy.EndStartCopy(result); WaitForCopy(snapshotCopy); Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); @@ -3048,7 +3048,7 @@ public void CloudPageBlobSnapshotTask() AssertAreEqual(snapshot1.Properties, snapshot1Clone.Properties); CloudPageBlob snapshotCopy = container.GetPageBlobReference("blob2"); - snapshotCopy.StartCopyFromBlobAsync(snapshot1, null, null, null, null).Wait(); + snapshotCopy.StartCopyAsync(snapshot1, null, null, null, null).Wait(); WaitForCopy(snapshotCopy); Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); diff --git a/Test/ClassLibraryCommon/Blob/CopyBlobTest.cs b/Test/ClassLibraryCommon/Blob/CopyBlobTest.cs index 673bb7a7d..fc502ed8a 100644 --- a/Test/ClassLibraryCommon/Blob/CopyBlobTest.cs +++ b/Test/ClassLibraryCommon/Blob/CopyBlobTest.cs @@ -71,11 +71,11 @@ public void CopyBlobUsingUnicodeBlobName() //Copy blobs over CloudBlockBlob blobAsciiDest = container.GetBlockBlobReference(_nonUnicodeBlobName + "_copy"); - string copyId = blobAsciiDest.StartCopyFromBlob(TestHelper.Defiddler(blobAsciiSource)); + string copyId = blobAsciiDest.StartCopy(TestHelper.Defiddler(blobAsciiSource)); WaitForCopy(blobAsciiDest); CloudBlockBlob blobUnicodeDest = container.GetBlockBlobReference(_unicodeBlobName + "_copy"); - copyId = blobUnicodeDest.StartCopyFromBlob(TestHelper.Defiddler(blobUnicodeSource)); + copyId = blobUnicodeDest.StartCopy(TestHelper.Defiddler(blobUnicodeSource)); WaitForCopy(blobUnicodeDest); Assert.AreEqual(CopyStatus.Success, blobUnicodeDest.CopyState.Status); @@ -159,7 +159,7 @@ private static void CloudBlockBlobCopy(bool sourceIsSas, bool destinationIsSas) } // Start copy and wait for completion - string copyId = copyDestination.StartCopyFromBlob(TestHelper.Defiddler(copySource)); + string copyId = copyDestination.StartCopy(TestHelper.Defiddler(copySource)); Assert.AreEqual(BlobType.BlockBlob, copyDestination.BlobType); WaitForCopy(destination); @@ -278,11 +278,11 @@ public void CloudBlockBlobCopyTestAPM() CloudBlockBlob copy = container.GetBlockBlobReference("copy"); using (AutoResetEvent waitHandle = new AutoResetEvent(false)) { - IAsyncResult result = copy.BeginStartCopyFromBlob(TestHelper.Defiddler(source), + IAsyncResult result = copy.BeginStartCopy(TestHelper.Defiddler(source), ar => waitHandle.Set(), null); waitHandle.WaitOne(); - string copyId = copy.EndStartCopyFromBlob(result); + string copyId = copy.EndStartCopy(result); Assert.AreEqual(BlobType.BlockBlob, copy.BlobType); WaitForCopy(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); @@ -355,7 +355,7 @@ public void CloudBlockBlobCopyTestTask() source.SetMetadataAsync().Wait(); CloudBlockBlob copy = container.GetBlockBlobReference("copy"); - string copyId = copy.StartCopyFromBlobAsync(TestHelper.Defiddler(source)).Result; + string copyId = copy.StartCopyAsync(TestHelper.Defiddler(source)).Result; Assert.AreEqual(BlobType.BlockBlob, copy.BlobType); WaitForCopyTask(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); @@ -423,7 +423,7 @@ public void CloudBlockBlobCopyTestWithMetadataOverride() CloudBlockBlob copy = container.GetBlockBlobReference("copy"); copy.Metadata["Test2"] = "value2"; - string copyId = copy.StartCopyFromBlob(TestHelper.Defiddler(source)); + string copyId = copy.StartCopy(TestHelper.Defiddler(source)); WaitForCopy(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); Assert.AreEqual(source.Uri.AbsolutePath, copy.CopyState.Source.AbsolutePath); @@ -495,7 +495,7 @@ public void CloudBlockBlobCopyFromSnapshotTest() Assert.AreNotEqual(source.Metadata["Test"], snapshot.Metadata["Test"], "Source and snapshot metadata should be independent"); CloudBlockBlob copy = container.GetBlockBlobReference("copy"); - copy.StartCopyFromBlob(TestHelper.Defiddler(snapshot)); + copy.StartCopy(TestHelper.Defiddler(snapshot)); WaitForCopy(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); Assert.AreEqual(data, DownloadText(copy, Encoding.UTF8), "Data inside copy of blob not similar"); @@ -543,7 +543,7 @@ public void CloudPageBlobCopyTest() source.SetMetadata(); CloudPageBlob copy = container.GetPageBlobReference("copy"); - string copyId = copy.StartCopyFromBlob(TestHelper.Defiddler(source)); + string copyId = copy.StartCopy(TestHelper.Defiddler(source)); Assert.AreEqual(BlobType.PageBlob, copy.BlobType); WaitForCopy(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); @@ -612,11 +612,11 @@ public void CloudPageBlobCopyTestAPM() CloudPageBlob copy = container.GetPageBlobReference("copy"); using (AutoResetEvent waitHandle = new AutoResetEvent(false)) { - IAsyncResult result = copy.BeginStartCopyFromBlob(TestHelper.Defiddler(source), + IAsyncResult result = copy.BeginStartCopy(TestHelper.Defiddler(source), ar => waitHandle.Set(), null); waitHandle.WaitOne(); - string copyId = copy.EndStartCopyFromBlob(result); + string copyId = copy.EndStartCopy(result); Assert.AreEqual(BlobType.PageBlob, copy.BlobType); WaitForCopy(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); @@ -689,7 +689,7 @@ public void CloudPageBlobCopyTestTask() source.SetMetadataAsync().Wait(); CloudPageBlob copy = container.GetPageBlobReference("copy"); - string copyId = copy.StartCopyFromBlobAsync(TestHelper.Defiddler(source)).Result; + string copyId = copy.StartCopyAsync(TestHelper.Defiddler(source)).Result; Assert.AreEqual(BlobType.PageBlob, copy.BlobType); WaitForCopyTask(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); @@ -757,7 +757,7 @@ public void CloudPageBlobCopyTestWithMetadataOverride() CloudPageBlob copy = container.GetPageBlobReference("copy"); copy.Metadata["Test2"] = "value2"; - string copyId = copy.StartCopyFromBlob(TestHelper.Defiddler(source)); + string copyId = copy.StartCopy(TestHelper.Defiddler(source)); WaitForCopy(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); Assert.AreEqual(source.Uri.AbsolutePath, copy.CopyState.Source.AbsolutePath); @@ -829,7 +829,7 @@ public void CloudPageBlobCopyFromSnapshotTest() Assert.AreNotEqual(source.Metadata["Test"], snapshot.Metadata["Test"], "Source and snapshot metadata should be independent"); CloudPageBlob copy = container.GetPageBlobReference("copy"); - copy.StartCopyFromBlob(TestHelper.Defiddler(snapshot)); + copy.StartCopy(TestHelper.Defiddler(snapshot)); WaitForCopy(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); Assert.AreEqual(data, DownloadText(copy, Encoding.UTF8), "Data inside copy of blob not similar"); @@ -878,13 +878,13 @@ public void CloudBlockBlobCopyWithSourceAccessCondition() source.FetchAttributes(); AccessCondition sourceAccessCondition1 = AccessCondition.GenerateIfNotModifiedSinceCondition(source.Properties.LastModified.Value); CloudBlockBlob copy1 = container.GetBlockBlobReference("copy1"); - copy1.StartCopyFromBlob(TestHelper.Defiddler(source), sourceAccessCondition1); + copy1.StartCopy(TestHelper.Defiddler(source), sourceAccessCondition1); WaitForCopy(copy1); Assert.AreEqual(CopyStatus.Success, copy1.CopyState.Status); AccessCondition sourceAccessCondition2 = AccessCondition.GenerateLeaseCondition(invalidLeaseId); CloudBlockBlob copy2 = container.GetBlockBlobReference("copy2"); - TestHelper.ExpectedException(() => copy2.StartCopyFromBlob(TestHelper.Defiddler(source), sourceAccessCondition2), "A lease condition cannot be specified on the source of a copy."); + TestHelper.ExpectedException(() => copy2.StartCopy(TestHelper.Defiddler(source), sourceAccessCondition2), "A lease condition cannot be specified on the source of a copy."); } finally { @@ -915,13 +915,13 @@ public void CloudPageBlobCopyWithSourceAccessCondition() source.FetchAttributes(); AccessCondition sourceAccessCondition1 = AccessCondition.GenerateIfNotModifiedSinceCondition(source.Properties.LastModified.Value); CloudPageBlob copy1 = container.GetPageBlobReference("copy1"); - copy1.StartCopyFromBlob(TestHelper.Defiddler(source), sourceAccessCondition1); + copy1.StartCopy(TestHelper.Defiddler(source), sourceAccessCondition1); WaitForCopy(copy1); Assert.AreEqual(CopyStatus.Success, copy1.CopyState.Status); AccessCondition sourceAccessCondition2 = AccessCondition.GenerateLeaseCondition(invalidLeaseId); CloudPageBlob copy2 = container.GetPageBlobReference("copy2"); - TestHelper.ExpectedException(() => copy2.StartCopyFromBlob(TestHelper.Defiddler(source), sourceAccessCondition2), "A lease condition cannot be specified on the source of a copy."); + TestHelper.ExpectedException(() => copy2.StartCopy(TestHelper.Defiddler(source), sourceAccessCondition2), "A lease condition cannot be specified on the source of a copy."); } finally { diff --git a/Test/ClassLibraryCommon/Blob/EscapingTests.cs b/Test/ClassLibraryCommon/Blob/EscapingTests.cs index 4988f640a..22936ea08 100644 --- a/Test/ClassLibraryCommon/Blob/EscapingTests.cs +++ b/Test/ClassLibraryCommon/Blob/EscapingTests.cs @@ -150,7 +150,7 @@ private void PrefixEscapingTest(string prefix, string blobName) // Copy blob verification. CloudBlockBlob copyBlob = container.GetBlockBlobReference(prefix + "/" + blobName + "copy"); - copyBlob.StartCopyFromBlob(blobInfo.Uri); + copyBlob.StartCopy(blobInfo.Uri); copyBlob.FetchAttributes(); } finally diff --git a/Test/ClassLibraryCommon/Blob/LeaseTests.cs b/Test/ClassLibraryCommon/Blob/LeaseTests.cs index 91f70d112..d27424c7e 100644 --- a/Test/ClassLibraryCommon/Blob/LeaseTests.cs +++ b/Test/ClassLibraryCommon/Blob/LeaseTests.cs @@ -4239,7 +4239,7 @@ private void BlobCreateExpectLeaseFailure(CloudBlockBlob testBlob, CloudBlockBlo expectedStatusCode, expectedErrorCode); TestHelper.ExpectedException( - () => testBlob.StartCopyFromBlob(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */), + () => testBlob.StartCopy(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */), description + " (Copy From)", expectedStatusCode, expectedErrorCode); @@ -4273,7 +4273,7 @@ private void BlobCreateExpectLeaseFailureTask(CloudBlockBlob testBlob, CloudBloc expectedStatusCode, expectedErrorCode); TestHelper.ExpectedExceptionTask( - testBlob.StartCopyFromBlobAsync(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */, new OperationContext()), + testBlob.StartCopyAsync(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */, new OperationContext()), description + " (Copy From)", expectedStatusCode, expectedErrorCode); @@ -4302,7 +4302,7 @@ private void BlobWriteExpectLeaseSuccess(CloudBlockBlob testBlob, CloudBlob sour testBlob.SetMetadata(testAccessCondition, null /* options */); testBlob.SetProperties(testAccessCondition, null /* options */); UploadText(testBlob, "No Problem", Encoding.UTF8, testAccessCondition, null /* options */); - testBlob.StartCopyFromBlob(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */); + testBlob.StartCopy(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */); while (testBlob.CopyState.Status == CopyStatus.Pending) { @@ -4336,9 +4336,9 @@ private void BlobWriteExpectLeaseSuccessAPM(CloudBlockBlob testBlob, CloudBlob s testBlob.EndSetProperties(result); UploadTextAPM(testBlob, "No Problem", Encoding.UTF8, testAccessCondition, null /* options */); - result = testBlob.BeginStartCopyFromBlob(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */, null /* operationContext */, ar=>waitHandle.Set(), null); + result = testBlob.BeginStartCopy(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */, null /* operationContext */, ar=>waitHandle.Set(), null); waitHandle.WaitOne(); - testBlob.EndStartCopyFromBlob(result); + testBlob.EndStartCopy(result); while (testBlob.CopyState.Status == CopyStatus.Pending) { @@ -4372,7 +4372,7 @@ private void BlobWriteExpectLeaseSuccessTask(CloudBlockBlob testBlob, CloudBlob testBlob.SetMetadataAsync(testAccessCondition, null /* options */, new OperationContext()).Wait(); testBlob.SetPropertiesAsync(testAccessCondition, null /* options */, new OperationContext()).Wait(); UploadTextTask(testBlob, "No Problem", Encoding.UTF8, testAccessCondition, null /* options */, new OperationContext()); - testBlob.StartCopyFromBlobAsync( + testBlob.StartCopyAsync( TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, diff --git a/Test/ClassLibraryCommon/Blob/MD5FlagsTest.cs b/Test/ClassLibraryCommon/Blob/MD5FlagsTest.cs index 7abd50022..673e5611d 100644 --- a/Test/ClassLibraryCommon/Blob/MD5FlagsTest.cs +++ b/Test/ClassLibraryCommon/Blob/MD5FlagsTest.cs @@ -72,53 +72,55 @@ public void StoreBlobContentMD5Test() { container.Create(); - ICloudBlob blob = container.GetBlockBlobReference("blob1"); + CloudBlockBlob blob1 = container.GetBlockBlobReference("blob1"); + using (Stream stream = new NonSeekableMemoryStream()) { - blob.UploadFromStream(stream, null, optionsWithMD5); + blob1.UploadFromStream(stream, null, optionsWithMD5); } - blob.FetchAttributes(); - Assert.IsNotNull(blob.Properties.ContentMD5); + blob1.FetchAttributes(); + Assert.IsNotNull(blob1.Properties.ContentMD5); - blob = container.GetBlockBlobReference("blob2"); + blob1 = container.GetBlockBlobReference("blob2"); using (Stream stream = new NonSeekableMemoryStream()) { - blob.UploadFromStream(stream, null, optionsWithNoMD5); + blob1.UploadFromStream(stream, null, optionsWithNoMD5); } - blob.FetchAttributes(); - Assert.IsNull(blob.Properties.ContentMD5); + blob1.FetchAttributes(); + Assert.IsNull(blob1.Properties.ContentMD5); - blob = container.GetBlockBlobReference("blob3"); + blob1 = container.GetBlockBlobReference("blob3"); using (Stream stream = new NonSeekableMemoryStream()) { - blob.UploadFromStream(stream); + blob1.UploadFromStream(stream); } - blob.FetchAttributes(); - Assert.IsNotNull(blob.Properties.ContentMD5); + blob1.FetchAttributes(); + Assert.IsNotNull(blob1.Properties.ContentMD5); - blob = container.GetPageBlobReference("blob4"); + CloudPageBlob blob2 = container.GetPageBlobReference("blob4"); + blob2 = container.GetPageBlobReference("blob4"); using (Stream stream = new MemoryStream()) { - blob.UploadFromStream(stream, null, optionsWithMD5); + blob2.UploadFromStream(stream, null, optionsWithMD5); } - blob.FetchAttributes(); - Assert.IsNotNull(blob.Properties.ContentMD5); + blob2.FetchAttributes(); + Assert.IsNotNull(blob2.Properties.ContentMD5); - blob = container.GetPageBlobReference("blob5"); + blob2 = container.GetPageBlobReference("blob5"); using (Stream stream = new MemoryStream()) { - blob.UploadFromStream(stream, null, optionsWithNoMD5); + blob2.UploadFromStream(stream, null, optionsWithNoMD5); } - blob.FetchAttributes(); - Assert.IsNull(blob.Properties.ContentMD5); + blob2.FetchAttributes(); + Assert.IsNull(blob2.Properties.ContentMD5); - blob = container.GetPageBlobReference("blob6"); + blob2 = container.GetPageBlobReference("blob6"); using (Stream stream = new MemoryStream()) { - blob.UploadFromStream(stream); + blob2.UploadFromStream(stream); } - blob.FetchAttributes(); - Assert.IsNull(blob.Properties.ContentMD5); + blob2.FetchAttributes(); + Assert.IsNull(blob2.Properties.ContentMD5); } finally { @@ -151,77 +153,77 @@ public void StoreBlobContentMD5TestAPM() using (AutoResetEvent waitHandle = new AutoResetEvent(false)) { IAsyncResult result; - ICloudBlob blob = container.GetBlockBlobReference("blob1"); + CloudBlockBlob blob1 = container.GetBlockBlobReference("blob1"); using (Stream stream = new NonSeekableMemoryStream()) { - result = blob.BeginUploadFromStream(stream, null, optionsWithMD5, null, + result = blob1.BeginUploadFromStream(stream, null, optionsWithMD5, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - blob.EndUploadFromStream(result); + blob1.EndUploadFromStream(result); } - blob.FetchAttributes(); - Assert.IsNotNull(blob.Properties.ContentMD5); + blob1.FetchAttributes(); + Assert.IsNotNull(blob1.Properties.ContentMD5); - blob = container.GetBlockBlobReference("blob2"); + blob1 = container.GetBlockBlobReference("blob2"); using (Stream stream = new NonSeekableMemoryStream()) { - result = blob.BeginUploadFromStream(stream, null, optionsWithNoMD5, null, + result = blob1.BeginUploadFromStream(stream, null, optionsWithNoMD5, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - blob.EndUploadFromStream(result); + blob1.EndUploadFromStream(result); } - blob.FetchAttributes(); - Assert.IsNull(blob.Properties.ContentMD5); + blob1.FetchAttributes(); + Assert.IsNull(blob1.Properties.ContentMD5); - blob = container.GetBlockBlobReference("blob3"); + blob1 = container.GetBlockBlobReference("blob3"); using (Stream stream = new NonSeekableMemoryStream()) { - result = blob.BeginUploadFromStream(stream, + result = blob1.BeginUploadFromStream(stream, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - blob.EndUploadFromStream(result); + blob1.EndUploadFromStream(result); } - blob.FetchAttributes(); - Assert.IsNotNull(blob.Properties.ContentMD5); + blob1.FetchAttributes(); + Assert.IsNotNull(blob1.Properties.ContentMD5); - blob = container.GetPageBlobReference("blob4"); + CloudPageBlob blob2 = container.GetPageBlobReference("blob4"); using (Stream stream = new MemoryStream()) { - result = blob.BeginUploadFromStream(stream, null, optionsWithMD5, null, + result = blob2.BeginUploadFromStream(stream, null, optionsWithMD5, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - blob.EndUploadFromStream(result); + blob2.EndUploadFromStream(result); } - blob.FetchAttributes(); - Assert.IsNotNull(blob.Properties.ContentMD5); + blob2.FetchAttributes(); + Assert.IsNotNull(blob2.Properties.ContentMD5); - blob = container.GetPageBlobReference("blob5"); + blob2 = container.GetPageBlobReference("blob5"); using (Stream stream = new MemoryStream()) { - result = blob.BeginUploadFromStream(stream, null, optionsWithNoMD5, null, + result = blob2.BeginUploadFromStream(stream, null, optionsWithNoMD5, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - blob.EndUploadFromStream(result); + blob2.EndUploadFromStream(result); } - blob.FetchAttributes(); - Assert.IsNull(blob.Properties.ContentMD5); + blob2.FetchAttributes(); + Assert.IsNull(blob2.Properties.ContentMD5); - blob = container.GetPageBlobReference("blob6"); + blob2 = container.GetPageBlobReference("blob6"); using (Stream stream = new MemoryStream()) { - result = blob.BeginUploadFromStream(stream, + result = blob2.BeginUploadFromStream(stream, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - blob.EndUploadFromStream(result); + blob2.EndUploadFromStream(result); } - blob.FetchAttributes(); - Assert.IsNull(blob.Properties.ContentMD5); + blob2.FetchAttributes(); + Assert.IsNull(blob2.Properties.ContentMD5); } } finally @@ -423,78 +425,78 @@ public void DisableContentMD5ValidationTestAPM() using (AutoResetEvent waitHandle = new AutoResetEvent(false)) { IAsyncResult result; - ICloudBlob blob = container.GetBlockBlobReference("blob1"); + CloudBlockBlob blob1 = container.GetBlockBlobReference("blob1"); using (Stream stream = new NonSeekableMemoryStream()) { - blob.UploadFromStream(stream, null, optionsWithMD5); + blob1.UploadFromStream(stream, null, optionsWithMD5); } using (Stream stream = new MemoryStream()) { - result = blob.BeginDownloadToStream(stream, null, optionsWithMD5, null, + result = blob1.BeginDownloadToStream(stream, null, optionsWithMD5, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - blob.EndDownloadToStream(result); - result = blob.BeginDownloadToStream(stream, null, optionsWithNoMD5, null, + blob1.EndDownloadToStream(result); + result = blob1.BeginDownloadToStream(stream, null, optionsWithNoMD5, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - blob.EndDownloadToStream(result); + blob1.EndDownloadToStream(result); - blob.Properties.ContentMD5 = "MDAwMDAwMDA="; - blob.SetProperties(); + blob1.Properties.ContentMD5 = "MDAwMDAwMDA="; + blob1.SetProperties(); - result = blob.BeginDownloadToStream(stream, null, optionsWithMD5, null, + result = blob1.BeginDownloadToStream(stream, null, optionsWithMD5, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); TestHelper.ExpectedException( - () => blob.EndDownloadToStream(result), + () => blob1.EndDownloadToStream(result), "Downloading a blob with invalid MD5 should fail", HttpStatusCode.OK); - result = blob.BeginDownloadToStream(stream, null, optionsWithNoMD5, null, + result = blob1.BeginDownloadToStream(stream, null, optionsWithNoMD5, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - blob.EndDownloadToStream(result); + blob1.EndDownloadToStream(result); } - blob = container.GetPageBlobReference("blob2"); + CloudPageBlob blob2 = container.GetPageBlobReference("blob2"); using (Stream stream = new MemoryStream()) { - blob.UploadFromStream(stream, null, optionsWithMD5); + blob2.UploadFromStream(stream, null, optionsWithMD5); } using (Stream stream = new MemoryStream()) { - result = blob.BeginDownloadToStream(stream, null, optionsWithMD5, null, + result = blob2.BeginDownloadToStream(stream, null, optionsWithMD5, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - blob.EndDownloadToStream(result); - result = blob.BeginDownloadToStream(stream, null, optionsWithNoMD5, null, + blob2.EndDownloadToStream(result); + result = blob2.BeginDownloadToStream(stream, null, optionsWithNoMD5, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - blob.EndDownloadToStream(result); + blob2.EndDownloadToStream(result); - blob.Properties.ContentMD5 = "MDAwMDAwMDA="; - blob.SetProperties(); + blob2.Properties.ContentMD5 = "MDAwMDAwMDA="; + blob2.SetProperties(); - result = blob.BeginDownloadToStream(stream, null, optionsWithMD5, null, + result = blob2.BeginDownloadToStream(stream, null, optionsWithMD5, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); TestHelper.ExpectedException( - () => blob.EndDownloadToStream(result), + () => blob2.EndDownloadToStream(result), "Downloading a blob with invalid MD5 should fail", HttpStatusCode.OK); - result = blob.BeginDownloadToStream(stream, null, optionsWithNoMD5, null, + result = blob2.BeginDownloadToStream(stream, null, optionsWithNoMD5, null, ar => waitHandle.Set(), null); waitHandle.WaitOne(); - blob.EndDownloadToStream(result); + blob2.EndDownloadToStream(result); } } } @@ -564,7 +566,30 @@ public void UseTransactionalMD5PutTest() Assert.AreEqual(3, checkCount); - CloudPageBlob pageBlob = container.GetPageBlobReference("blob2"); + checkCount = 0; + CloudAppendBlob appendBlob = container.GetAppendBlobReference("blob2"); + appendBlob.CreateOrReplace(); + checkCount = 0; + using (Stream blockData = new MemoryStream(buffer)) + { + lastCheckMD5 = "invalid_md5"; + appendBlob.AppendBlock(blockData, null, null, optionsWithNoMD5, opContextWithMD5Check); + Assert.IsNull(lastCheckMD5); + + lastCheckMD5 = "invalid_md5"; + blockData.Seek(0, SeekOrigin.Begin); + appendBlob.AppendBlock(blockData, null, null, optionsWithMD5, opContextWithMD5Check); + Assert.AreEqual(md5, lastCheckMD5); + + lastCheckMD5 = "invalid_md5"; + blockData.Seek(0, SeekOrigin.Begin); + appendBlob.AppendBlock(blockData, md5, null, optionsWithNoMD5, opContextWithMD5Check); + Assert.AreEqual(md5, lastCheckMD5); + } + + Assert.AreEqual(3, checkCount); + + CloudPageBlob pageBlob = container.GetPageBlobReference("blob3"); pageBlob.Create(buffer.Length); checkCount = 0; using (Stream pageData = new MemoryStream(buffer)) @@ -587,7 +612,7 @@ public void UseTransactionalMD5PutTest() Assert.AreEqual(3, checkCount); lastCheckMD5 = null; - blockBlob = container.GetBlockBlobReference("blob3"); + blockBlob = container.GetBlockBlobReference("blob4"); checkCount = 0; using (Stream blobStream = blockBlob.OpenWrite(null, optionsWithMD5, opContextWithMD5Check)) { @@ -598,7 +623,7 @@ public void UseTransactionalMD5PutTest() Assert.AreEqual(1, checkCount); lastCheckMD5 = "invalid_md5"; - blockBlob = container.GetBlockBlobReference("blob4"); + blockBlob = container.GetBlockBlobReference("blob5"); checkCount = 0; using (Stream blobStream = blockBlob.OpenWrite(null, optionsWithNoMD5, opContextWithMD5Check)) { @@ -609,7 +634,7 @@ public void UseTransactionalMD5PutTest() Assert.AreEqual(1, checkCount); lastCheckMD5 = null; - pageBlob = container.GetPageBlobReference("blob5"); + pageBlob = container.GetPageBlobReference("blob6"); checkCount = 0; using (Stream blobStream = pageBlob.OpenWrite(buffer.Length * 3, null, optionsWithMD5, opContextWithMD5Check)) { @@ -620,7 +645,7 @@ public void UseTransactionalMD5PutTest() Assert.AreEqual(1, checkCount); lastCheckMD5 = "invalid_md5"; - pageBlob = container.GetPageBlobReference("blob6"); + pageBlob = container.GetPageBlobReference("blob7"); checkCount = 0; using (Stream blobStream = pageBlob.OpenWrite(buffer.Length * 3, null, optionsWithNoMD5, opContextWithMD5Check)) { @@ -711,7 +736,41 @@ public void UseTransactionalMD5PutTestAPM() Assert.AreEqual(3, checkCount); - CloudPageBlob pageBlob = container.GetPageBlobReference("blob2"); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("blob2"); + appendBlob.CreateOrReplace(); + checkCount = 0; + using (Stream blockData = new MemoryStream(buffer)) + { + lastCheckMD5 = "invalid_md5"; + result = appendBlob.BeginAppendBlock(blockData, null, null, optionsWithNoMD5, opContextWithMD5Check, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + appendBlob.EndAppendBlock(result); + Assert.IsNull(lastCheckMD5); + + lastCheckMD5 = "invalid_md5"; + blockData.Seek(0, SeekOrigin.Begin); + result = appendBlob.BeginAppendBlock(blockData, null, null, optionsWithMD5, opContextWithMD5Check, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + appendBlob.EndAppendBlock(result); + Assert.AreEqual(md5, lastCheckMD5); + + lastCheckMD5 = "invalid_md5"; + blockData.Seek(0, SeekOrigin.Begin); + result = appendBlob.BeginAppendBlock(blockData, md5, null, optionsWithNoMD5, opContextWithMD5Check, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + appendBlob.EndAppendBlock(result); + Assert.AreEqual(md5, lastCheckMD5); + } + + Assert.AreEqual(3, checkCount); + + CloudPageBlob pageBlob = container.GetPageBlobReference("blob3"); pageBlob.Create(buffer.Length); checkCount = 0; using (Stream pageData = new MemoryStream(buffer)) @@ -842,7 +901,58 @@ public void UseTransactionalMD5GetTest() Assert.AreEqual(9, checkCount); - CloudPageBlob pageBlob = container.GetPageBlobReference("blob2"); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("blob2"); + using (Stream blobStream = appendBlob.OpenWrite(true)) + { + blobStream.Write(buffer, 0, buffer.Length); + blobStream.Write(buffer, 0, buffer.Length); + } + + checkCount = 0; + using (Stream stream = new MemoryStream()) + { + lastCheckMD5 = null; + appendBlob.DownloadToStream(stream, null, optionsWithNoMD5, opContextWithMD5Check); + Assert.IsNotNull(lastCheckMD5); + + lastCheckMD5 = null; + appendBlob.DownloadToStream(stream, null, optionsWithMD5, opContextWithMD5Check); + Assert.IsNotNull(lastCheckMD5); + + lastCheckMD5 = "invalid_md5"; + blockBlob.DownloadRangeToStream(stream, buffer.Length, buffer.Length, null, optionsWithNoMD5, opContextWithMD5Check); + Assert.IsNull(lastCheckMD5); + + lastCheckMD5 = "invalid_md5"; + appendBlob.DownloadRangeToStream(stream, buffer.Length, buffer.Length, null, optionsWithMD5, opContextWithMD5Check); + Assert.AreEqual(md5, lastCheckMD5); + + lastCheckMD5 = "invalid_md5"; + appendBlob.DownloadRangeToStream(stream, 1024, 4 * 1024 * 1024 + 1, null, optionsWithNoMD5, opContextWithMD5Check); + Assert.IsNull(lastCheckMD5); + + StorageException storageEx = TestHelper.ExpectedException( + () => appendBlob.DownloadRangeToStream(stream, 1024, 4 * 1024 * 1024 + 1, null, optionsWithMD5, opContextWithMD5Check), + "Downloading more than 4MB with transactional MD5 should not be supported"); + Assert.IsInstanceOfType(storageEx.InnerException, typeof(ArgumentOutOfRangeException)); + + lastCheckMD5 = null; + using (Stream blobStream = appendBlob.OpenRead(null, optionsWithMD5, opContextWithMD5Check)) + { + blobStream.CopyTo(stream); + Assert.IsNotNull(lastCheckMD5); + } + + lastCheckMD5 = "invalid_md5"; + using (Stream blobStream = appendBlob.OpenRead(null, optionsWithNoMD5, opContextWithMD5Check)) + { + blobStream.CopyTo(stream); + Assert.IsNull(lastCheckMD5); + } + } + Assert.AreEqual(9, checkCount); + + CloudPageBlob pageBlob = container.GetPageBlobReference("blob3"); using (Stream blobStream = pageBlob.OpenWrite(buffer.Length * 2)) { blobStream.Write(buffer, 0, buffer.Length); @@ -1025,7 +1135,89 @@ public void UseTransactionalMD5GetTestAPM() Assert.AreEqual(9, checkCount); - CloudPageBlob pageBlob = container.GetPageBlobReference("blob2"); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("blob2"); + using (Stream blobStream = appendBlob.OpenWrite(true)) + { + blobStream.Write(buffer, 0, buffer.Length); + blobStream.Write(buffer, 0, buffer.Length); + } + + checkCount = 0; + using (Stream stream = new MemoryStream()) + { + lastCheckMD5 = null; + result = appendBlob.BeginDownloadToStream(stream, null, optionsWithNoMD5, opContextWithMD5Check, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + appendBlob.EndDownloadRangeToStream(result); + Assert.IsNotNull(lastCheckMD5); + + lastCheckMD5 = null; + result = appendBlob.BeginDownloadToStream(stream, null, optionsWithMD5, opContextWithMD5Check, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + appendBlob.EndDownloadRangeToStream(result); + Assert.IsNotNull(lastCheckMD5); + + lastCheckMD5 = "invalid_md5"; + result = appendBlob.BeginDownloadRangeToStream(stream, buffer.Length, buffer.Length, null, optionsWithNoMD5, opContextWithMD5Check, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + appendBlob.EndDownloadRangeToStream(result); + Assert.IsNull(lastCheckMD5); + + lastCheckMD5 = "invalid_md5"; + result = appendBlob.BeginDownloadRangeToStream(stream, buffer.Length, buffer.Length, null, optionsWithMD5, opContextWithMD5Check, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + appendBlob.EndDownloadRangeToStream(result); + Assert.AreEqual(md5, lastCheckMD5); + + lastCheckMD5 = "invalid_md5"; + result = appendBlob.BeginDownloadRangeToStream(stream, 1024, 4 * 1024 * 1024 + 1, null, optionsWithNoMD5, opContextWithMD5Check, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + appendBlob.EndDownloadRangeToStream(result); + Assert.IsNull(lastCheckMD5); + + result = appendBlob.BeginDownloadRangeToStream(stream, 1024, 4 * 1024 * 1024 + 1, null, optionsWithMD5, opContextWithMD5Check, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + StorageException storageEx = TestHelper.ExpectedException( + () => appendBlob.EndDownloadRangeToStream(result), + "Downloading more than 4MB with transactional MD5 should not be supported"); + Assert.IsInstanceOfType(storageEx.InnerException, typeof(ArgumentOutOfRangeException)); + + lastCheckMD5 = null; + result = appendBlob.BeginOpenRead(null, optionsWithMD5, opContextWithMD5Check, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + using (Stream blobStream = appendBlob.EndOpenRead(result)) + { + blobStream.CopyTo(stream); + Assert.IsNotNull(lastCheckMD5); + } + + lastCheckMD5 = "invalid_md5"; + result = appendBlob.BeginOpenRead(null, optionsWithNoMD5, opContextWithMD5Check, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + using (Stream blobStream = appendBlob.EndOpenRead(result)) + { + blobStream.CopyTo(stream); + Assert.IsNull(lastCheckMD5); + } + } + + CloudPageBlob pageBlob = container.GetPageBlobReference("blob3"); using (Stream blobStream = pageBlob.OpenWrite(buffer.Length * 2)) { blobStream.Write(buffer, 0, buffer.Length); diff --git a/Test/ClassLibraryCommon/Blob/Protocol/BlobClientTests.cs b/Test/ClassLibraryCommon/Blob/Protocol/BlobClientTests.cs index a29ff8c57..04288c988 100644 --- a/Test/ClassLibraryCommon/Blob/Protocol/BlobClientTests.cs +++ b/Test/ClassLibraryCommon/Blob/Protocol/BlobClientTests.cs @@ -1000,7 +1000,7 @@ public void CopyFromToRestoreSnapshot(BlobContext context, string containerName, Assert.IsNotNull(snapshot.SnapshotTime); BlobTestBase.UploadText(blob, newText, Encoding.UTF8); - Uri sourceUri = new Uri(snapshot.Uri.AbsoluteUri + "?snapshot=" + BlobRequest.ConvertDateTimeToSnapshotString(snapshot.SnapshotTime.Value)); + Uri sourceUri = new Uri(snapshot.Uri.AbsoluteUri + "?snapshot=" + Request.ConvertDateTimeToSnapshotString(snapshot.SnapshotTime.Value)); OperationContext opContext = new OperationContext(); HttpWebRequest request = BlobHttpWebRequestFactory.CopyFrom(blob.Uri, 30, sourceUri, null, null, opContext); Assert.IsTrue(request != null, "Failed to create HttpWebRequest"); diff --git a/Test/ClassLibraryCommon/Blob/SASTests.cs b/Test/ClassLibraryCommon/Blob/SASTests.cs index 085263d2f..57568d262 100644 --- a/Test/ClassLibraryCommon/Blob/SASTests.cs +++ b/Test/ClassLibraryCommon/Blob/SASTests.cs @@ -68,10 +68,14 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm { blob = container.GetBlockBlobReference(blob.Name); } - else + else if (blob.BlobType == BlobType.PageBlob) { blob = container.GetPageBlobReference(blob.Name); } + else + { + blob = container.GetAppendBlobReference(blob.Name); + } } else { @@ -79,10 +83,14 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm { blob = new CloudBlockBlob(credentials.TransformUri(blob.Uri)); } - else + else if (blob.BlobType == BlobType.PageBlob) { blob = new CloudPageBlob(credentials.TransformUri(blob.Uri)); } + else + { + blob = new CloudAppendBlob(credentials.TransformUri(blob.Uri)); + } } if (container != null) @@ -103,7 +111,7 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm if ((permissions & SharedAccessBlobPermissions.Read) == SharedAccessBlobPermissions.Read) { blob.FetchAttributes(); - + // Test headers if (headers != null) { @@ -166,11 +174,14 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm } } +#pragma warning disable 0618 private static void TestBlobSAS(CloudBlob testBlob, SharedAccessBlobPermissions permissions, SharedAccessBlobHeaders headers) { TestBlobSAS(testBlob, permissions, headers, null); } +#pragma warning restore 0618 + [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] private static void TestBlobSAS(CloudBlob testBlob, SharedAccessBlobPermissions permissions, SharedAccessBlobHeaders headers, string sasVersion) { UploadText(testBlob, "blob", Encoding.UTF8); @@ -259,6 +270,10 @@ public void CloudBlobContainerSASCombinations() CloudPageBlob testPageBlob = this.testContainer.GetPageBlobReference("pageblob" + i); UploadText(testPageBlob, "blob", Encoding.UTF8); SASTests.TestAccess(sasToken, permissions, null, this.testContainer, testPageBlob); + + CloudAppendBlob testAppendBlob = this.testContainer.GetAppendBlobReference("appendblob" + i); + UploadText(testAppendBlob, "blob", Encoding.UTF8); + SASTests.TestAccess(sasToken, permissions, null, this.testContainer, testAppendBlob); } } @@ -276,6 +291,9 @@ public void CloudBlobContainerPublicAccess() CloudPageBlob testPageBlob = this.testContainer.GetPageBlobReference("pageblob"); UploadText(testPageBlob, "blob", Encoding.UTF8); + CloudAppendBlob testAppendBlob = this.testContainer.GetAppendBlobReference("appendblob"); + UploadText(testAppendBlob, "blob", Encoding.UTF8); + BlobContainerPermissions permissions = new BlobContainerPermissions(); permissions.PublicAccess = BlobContainerPublicAccessType.Container; @@ -283,12 +301,40 @@ public void CloudBlobContainerPublicAccess() Thread.Sleep(35 * 1000); SASTests.TestAccess(null, SharedAccessBlobPermissions.List | SharedAccessBlobPermissions.Read, null, this.testContainer, testBlockBlob); SASTests.TestAccess(null, SharedAccessBlobPermissions.List | SharedAccessBlobPermissions.Read, null, this.testContainer, testPageBlob); + SASTests.TestAccess(null, SharedAccessBlobPermissions.List | SharedAccessBlobPermissions.Read, null, this.testContainer, testAppendBlob); permissions.PublicAccess = BlobContainerPublicAccessType.Blob; this.testContainer.SetPermissions(permissions); Thread.Sleep(35 * 1000); SASTests.TestAccess(null, SharedAccessBlobPermissions.Read, null, this.testContainer, testBlockBlob); SASTests.TestAccess(null, SharedAccessBlobPermissions.Read, null, this.testContainer, testPageBlob); + SASTests.TestAccess(null, SharedAccessBlobPermissions.Read, null, this.testContainer, testAppendBlob); + } + + [TestMethod] + [Description("Create client from storage account with anonymous creds.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudBlobCreateClientWithAnonymousCreds() + { + CloudBlockBlob testBlockBlob = this.testContainer.GetBlockBlobReference("blockblob"); + UploadText(testBlockBlob, "blob", Encoding.UTF8); + + BlobContainerPermissions permissions = new BlobContainerPermissions(); + + permissions.PublicAccess = BlobContainerPublicAccessType.Container; + this.testContainer.SetPermissions(permissions); + Thread.Sleep(35 * 1000); + + string blobUri = testBlockBlob.ServiceClient.BaseUri.AbsoluteUri; + string accountString = "BlobEndpoint=" + blobUri; + + CloudStorageAccount acc = CloudStorageAccount.Parse(accountString); + CloudBlobClient client = acc.CreateCloudBlobClient(); + CloudBlobContainer container = client.GetContainerReference(this.testContainer.Name); + container.ListBlobs().ToArray(); } [TestMethod] @@ -305,6 +351,9 @@ public void CloudBlobContainerPolicy() CloudPageBlob testPageBlob = this.testContainer.GetPageBlobReference("pageblob"); UploadText(testPageBlob, "blob", Encoding.UTF8); + CloudAppendBlob testAppendBlob = this.testContainer.GetAppendBlobReference("appendblob"); + UploadText(testAppendBlob, "blob", Encoding.UTF8); + SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy() { SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), @@ -323,9 +372,13 @@ public void CloudBlobContainerPolicy() sasToken = testPageBlob.GetSharedAccessSignature(null, "testpolicy"); SASTests.TestAccess(sasToken, policy.Permissions, null, null, testPageBlob); + sasToken = testAppendBlob.GetSharedAccessSignature(null, "testpolicy"); + SASTests.TestAccess(sasToken, policy.Permissions, null, null, testAppendBlob); + sasToken = this.testContainer.GetSharedAccessSignature(null, "testpolicy"); SASTests.TestAccess(sasToken, policy.Permissions, null, this.testContainer, testBlockBlob); SASTests.TestAccess(sasToken, policy.Permissions, null, this.testContainer, testPageBlob); + SASTests.TestAccess(sasToken, policy.Permissions, null, this.testContainer, testAppendBlob); } [TestMethod] @@ -360,6 +413,22 @@ public void CloudPageBlobSASCombinations() } } + [TestMethod] + [Description("Test all combinations of blob permissions against an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobSASCombinations() + { + for (int i = 1; i < 8; i++) + { + CloudAppendBlob testBlob = this.testContainer.GetAppendBlobReference("blob" + i); + SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)i; + TestBlobSAS(testBlob, permissions, null); + } + } + [TestMethod] [Description("Test all combinations of blob permissions against a block blob")] [TestCategory(ComponentCategory.Blob)] @@ -410,12 +479,38 @@ public void CloudPageBlobSASHeaders() } } + [TestMethod] + [Description("Test all combinations of blob permissions against an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudAppendBlobSASHeaders() + { + for (int i = 1; i < 8; i++) + { + CloudAppendBlob testBlob = this.testContainer.GetAppendBlobReference("blob" + i); + SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)i; + SharedAccessBlobHeaders headers = new SharedAccessBlobHeaders() + { + CacheControl = "no-transform", + ContentDisposition = "attachment", + ContentEncoding = "gzip", + ContentLanguage = "tr,en", + ContentType = "text/html" + }; + + TestBlobSAS(testBlob, permissions, headers); + } + } + [TestMethod] [Description("Test access of container with generation of 2012-02-12 SAS token.")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] public void CloudBlobContainer20120212SASVersion() { // Create a policy with read/write access and get SAS. @@ -438,6 +533,7 @@ public void CloudBlobContainer20120212SASVersion() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] public void CloudBlockBlob20120212SASVersion() { for (int i = 1; i < 8; i++) @@ -454,6 +550,7 @@ public void CloudBlockBlob20120212SASVersion() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] public void CloudPageBlob20120212SASVersion() { for (int i = 1; i < 8; i++) @@ -464,12 +561,30 @@ public void CloudPageBlob20120212SASVersion() } } + [TestMethod] + [Description("Test all combinations of permissions using generation of 2012-02-12 SAS tokens for append blobs.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] + public void CloudAppendBlob20120212SASVersion() + { + for (int i = 1; i < 8; i++) + { + CloudAppendBlob testBlob = this.testContainer.GetAppendBlobReference("blob" + i); + SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)i; + TestBlobSAS(testBlob, permissions, null, Constants.VersionConstants.February2012); + } + } + [TestMethod] [Description("Test invalid SAS Version for page blobs.")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] public void CloudPageBlobInvalidSASVersion() { try @@ -491,6 +606,7 @@ public void CloudPageBlobInvalidSASVersion() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] public void CloudBlockBlobInvalidSASVersion() { try @@ -506,12 +622,35 @@ public void CloudBlockBlobInvalidSASVersion() } } + [TestMethod] + [Description("Test invalid SAS Version for append blobs.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] + public void CloudAppendBlobInvalidSASVersion() + { + try + { + CloudAppendBlob testBlob = this.testContainer.GetAppendBlobReference("blob" + 1); + SharedAccessBlobPermissions permissions = (SharedAccessBlobPermissions)1; + TestBlobSAS(testBlob, permissions, null, "2012-02-29"); + Assert.Fail(); + } + catch (ArgumentException e) + { + Assert.AreEqual(SR.InvalidSASVersion, e.Message); + } + } + [TestMethod] [Description("Negative test for empty SAS Version.")] [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] public void CloudPageBlobEmptySASVersion() { try @@ -533,6 +672,7 @@ public void CloudPageBlobEmptySASVersion() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] public void CloudPageBlobHeaders20120212SASVersion() { SharedAccessBlobHeaders headers = new SharedAccessBlobHeaders() @@ -569,7 +709,7 @@ public void CloudBlobSASApiVersionQueryParam() try { container.Create(); - ICloudBlob blob; + CloudBlob blob; SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy() { @@ -584,6 +724,9 @@ public void CloudBlobSASApiVersionQueryParam() CloudPageBlob pageBlob = container.GetPageBlobReference("pb"); pageBlob.Create(0); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("ab"); + appendBlob.CreateOrReplace(); + string blockBlobToken = blockBlob.GetSharedAccessSignature(policy); StorageCredentials blockBlobSAS = new StorageCredentials(blockBlobToken); Uri blockBlobSASUri = blockBlobSAS.TransformUri(blockBlob.Uri); @@ -594,29 +737,39 @@ public void CloudBlobSASApiVersionQueryParam() Uri pageBlobSASUri = pageBlobSAS.TransformUri(pageBlob.Uri); StorageUri pageBlobSASStorageUri = pageBlobSAS.TransformUri(pageBlob.StorageUri); + string appendBlobToken = appendBlob.GetSharedAccessSignature(policy); + StorageCredentials appendBlobSAS = new StorageCredentials(appendBlobToken); + Uri appendBlobSASUri = appendBlobSAS.TransformUri(appendBlob.Uri); + StorageUri appendBlobSASStorageUri = appendBlobSAS.TransformUri(appendBlob.StorageUri); + OperationContext apiVersionCheckContext = new OperationContext(); apiVersionCheckContext.SendingRequest += (sender, e) => { Assert.IsTrue(e.Request.RequestUri.Query.Contains("api-version")); }; - blob = container.ServiceClient.GetBlobReferenceFromServer(blockBlobSASUri, operationContext: apiVersionCheckContext); - Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); + blob = new CloudBlob(blockBlobSASUri); + blob.FetchAttributes(operationContext: apiVersionCheckContext); + Assert.AreEqual(blob.BlobType, BlobType.BlockBlob); Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(blockBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); - blob = container.ServiceClient.GetBlobReferenceFromServer(pageBlobSASUri, operationContext: apiVersionCheckContext); - Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); + blob = new CloudBlob(pageBlobSASUri); + blob.FetchAttributes(operationContext: apiVersionCheckContext); + Assert.AreEqual(blob.BlobType, BlobType.PageBlob); Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); - blob = container.ServiceClient.GetBlobReferenceFromServer(blockBlobSASStorageUri, operationContext: apiVersionCheckContext); - Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); + blob = new CloudBlob(blockBlobSASStorageUri, null, null); + blob.FetchAttributes(operationContext: apiVersionCheckContext); + Assert.AreEqual(blob.BlobType, BlobType.BlockBlob); Assert.IsTrue(blob.StorageUri.Equals(blockBlob.StorageUri)); - blob = container.ServiceClient.GetBlobReferenceFromServer(pageBlobSASStorageUri, operationContext: apiVersionCheckContext); - Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); + blob = new CloudBlob(pageBlobSASStorageUri, null, null); + blob.FetchAttributes(operationContext: apiVersionCheckContext); + Assert.AreEqual(blob.BlobType, BlobType.PageBlob); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + } finally { diff --git a/Test/ClassLibraryCommon/Core/MultiBufferMemoryStreamTests.cs b/Test/ClassLibraryCommon/Core/MultiBufferMemoryStreamTests.cs index 095dcd533..6a71c73df 100644 --- a/Test/ClassLibraryCommon/Core/MultiBufferMemoryStreamTests.cs +++ b/Test/ClassLibraryCommon/Core/MultiBufferMemoryStreamTests.cs @@ -230,5 +230,81 @@ public void MultiBufferMemoryStreamReadSeekSetLengthTestAPM() } } } + + [TestMethod] + [Description("Ensure MultiBufferMemoryStream provided by user is not closed after upload.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void EnsureMultiBufferMemoryStreamIsNotClosed() + { + byte[] buffer = GetRandomBuffer(1 * 1024 * 1024); + CloudBlobClient blobClient = GenerateCloudBlobClient(); + CloudBlobContainer container = blobClient.GetContainerReference(Guid.NewGuid().ToString("N")); + + try + { + container.Create(); + + CloudBlockBlob blob = container.GetBlockBlobReference("blob1"); + using (MultiBufferMemoryStream originalBlob = new MultiBufferMemoryStream(null)) + { + originalBlob.Write(buffer, 0, buffer.Length); + originalBlob.Seek(0, SeekOrigin.Begin); + + blob.PutBlock(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), originalBlob, null); + + Assert.IsTrue(originalBlob.CanSeek); + } + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Ensure MultiBufferMemoryStream provided by user is not closed after upload.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void EnsureMultiBufferMemoryStreamIsNotClosedAPM() + { + byte[] buffer = GetRandomBuffer(1 * 1024 * 1024); + CloudBlobClient blobClient = GenerateCloudBlobClient(); + CloudBlobContainer container = blobClient.GetContainerReference(Guid.NewGuid().ToString("N")); + + try + { + container.Create(); + + CloudBlockBlob blob = container.GetBlockBlobReference("blob1"); + using (MultiBufferMemoryStream originalBlob = new MultiBufferMemoryStream(null)) + { + originalBlob.Write(buffer, 0, buffer.Length); + originalBlob.Seek(0, SeekOrigin.Begin); + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + ICancellableAsyncResult result = blob.BeginPutBlock( + Convert.ToBase64String(Guid.NewGuid().ToByteArray()), + originalBlob, + null, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + blob.EndPutBlock(result); + } + + Assert.IsTrue(originalBlob.CanSeek); + } + } + finally + { + container.DeleteIfExists(); + } + } } } diff --git a/Test/ClassLibraryCommon/File/CloudFileClientTest.cs b/Test/ClassLibraryCommon/File/CloudFileClientTest.cs index 5c52eb7e4..195dfcab8 100644 --- a/Test/ClassLibraryCommon/File/CloudFileClientTest.cs +++ b/Test/ClassLibraryCommon/File/CloudFileClientTest.cs @@ -126,6 +126,7 @@ public void CloudFileClientListShares() foreach (CloudFileShare share in results) { + Assert.IsTrue(share.Properties.Quota.HasValue); if (shareNames.Remove(share.Name)) { share.Delete(); diff --git a/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs b/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs index e108c0d53..8d73d5f3f 100644 --- a/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs +++ b/Test/ClassLibraryCommon/File/CloudFileDirectoryTest.cs @@ -341,6 +341,220 @@ public void CloudFileDirectoryDeleteIfExistsAPM() } } + [TestMethod] + [Description("Verify that creating a file directory can also set its metadata")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileDirectoryCreateWithMetadata() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + CloudFileDirectory directory = share.GetRootDirectoryReference().GetDirectoryReference("directory1"); + directory.Metadata["key1"] = "value1"; + directory.Create(); + + CloudFileDirectory directory2 = share.GetRootDirectoryReference().GetDirectoryReference("directory1"); + directory2.FetchAttributes(); + Assert.AreEqual(1, directory2.Metadata.Count); + Assert.AreEqual("value1", directory2.Metadata["key1"]); + } + finally + { + share.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Verify that a file directory's metadata can be updated")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileDirectorySetMetadata() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + CloudFileDirectory directory = share.GetRootDirectoryReference().GetDirectoryReference("directory1"); + directory.Create(); + + CloudFileDirectory directory2 = share.GetRootDirectoryReference().GetDirectoryReference("directory1"); + directory2.FetchAttributes(); + Assert.AreEqual(0, directory2.Metadata.Count); + + directory.Metadata["key1"] = null; + StorageException e = TestHelper.ExpectedException( + () => directory.SetMetadata(), + "Metadata keys should have a non-null value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + directory.Metadata["key1"] = ""; + e = TestHelper.ExpectedException( + () => directory.SetMetadata(), + "Metadata keys should have a non-empty value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + directory.Metadata["key1"] = "value1"; + directory.SetMetadata(); + + directory2.FetchAttributes(); + Assert.AreEqual(1, directory2.Metadata.Count); + Assert.AreEqual("value1", directory2.Metadata["key1"]); + + directory.Metadata.Clear(); + directory.SetMetadata(); + + directory2.FetchAttributes(); + Assert.AreEqual(0, directory2.Metadata.Count); + } + finally + { + share.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Verify that a file directory's metadata can be updated")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileDirectorySetMetadataAPM() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + CloudFileDirectory directory = share.GetRootDirectoryReference().GetDirectoryReference("directory1"); + directory.Create(); + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + CloudFileDirectory directory2 = share.GetRootDirectoryReference().GetDirectoryReference("directory1"); + IAsyncResult result = directory2.BeginFetchAttributes( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + directory2.EndFetchAttributes(result); + Assert.AreEqual(0, directory2.Metadata.Count); + + directory.Metadata["key1"] = null; + result = directory.BeginSetMetadata( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + Exception e = TestHelper.ExpectedException( + () => directory.EndSetMetadata(result), + "Metadata keys should have a non-null value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + directory.Metadata["key1"] = ""; + result = directory.BeginSetMetadata( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + e = TestHelper.ExpectedException( + () => directory.EndSetMetadata(result), + "Metadata keys should have a non-empty value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + directory.Metadata["key1"] = "value1"; + result = directory.BeginSetMetadata( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + directory.EndSetMetadata(result); + + result = directory2.BeginFetchAttributes( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + directory2.EndFetchAttributes(result); + Assert.AreEqual(1, directory2.Metadata.Count); + Assert.AreEqual("value1", directory2.Metadata["key1"]); + + directory.Metadata.Clear(); + result = directory.BeginSetMetadata( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + directory.EndSetMetadata(result); + + result = directory2.BeginFetchAttributes( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + directory2.EndFetchAttributes(result); + Assert.AreEqual(0, directory2.Metadata.Count); + } + } + finally + { + share.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Verify that a file directory's metadata can be updated")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileDirectorySetMetadataTask() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.CreateAsync().Wait(); + + CloudFileDirectory directory = share.GetRootDirectoryReference().GetDirectoryReference("directory1"); + directory.CreateAsync().Wait(); + + CloudFileDirectory directory2 = share.GetRootDirectoryReference().GetDirectoryReference("directory1"); + directory2.FetchAttributesAsync().Wait(); + Assert.AreEqual(0, directory2.Metadata.Count); + + directory.Metadata["key1"] = null; + StorageException e = TestHelper.ExpectedExceptionTask( + directory.SetMetadataAsync(), + "Metadata keys should have a non-null value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + directory.Metadata["key1"] = ""; + e = TestHelper.ExpectedExceptionTask( + directory.SetMetadataAsync(), + "Metadata keys should have a non-empty value"); + Assert.IsInstanceOfType(e.InnerException, typeof(ArgumentException)); + + directory.Metadata["key1"] = "value1"; + directory.SetMetadataAsync().Wait(); + + directory2.FetchAttributesAsync().Wait(); + Assert.AreEqual(1, directory2.Metadata.Count); + Assert.AreEqual("value1", directory2.Metadata["key1"]); + + directory.Metadata.Clear(); + directory.SetMetadataAsync().Wait(); + + directory2.FetchAttributesAsync().Wait(); + Assert.AreEqual(0, directory2.Metadata.Count); + } + finally + { + share.DeleteIfExistsAsync().Wait(); + } + } +#endif + [TestMethod] [Description("CloudFileDirectory listing")] [TestCategory(ComponentCategory.File)] diff --git a/Test/ClassLibraryCommon/File/CloudFileShareTest.cs b/Test/ClassLibraryCommon/File/CloudFileShareTest.cs index ebd3ac14f..74562fe04 100644 --- a/Test/ClassLibraryCommon/File/CloudFileShareTest.cs +++ b/Test/ClassLibraryCommon/File/CloudFileShareTest.cs @@ -17,6 +17,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.WindowsAzure.Storage.Auth; +using Microsoft.WindowsAzure.Storage.File.Protocol; using System; using System.Collections.Generic; using System.Globalization; @@ -700,6 +701,679 @@ public void CloudFileShareSetMetadataTask() } #endif + [TestMethod] + [Description("Set share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareSetPermissions() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + FileSharePermissions permissions = share.GetPermissions(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + permissions.SharedAccessPolicies.Add("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + share.SetPermissions(permissions); + Thread.Sleep(30 * 1000); + + CloudFileShare share2 = share.ServiceClient.GetShareReference(share.Name); + permissions = share2.GetPermissions(); + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.HasValue); + Assert.AreEqual(start, permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.Value.UtcDateTime); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.HasValue); + Assert.AreEqual(expiry, permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.Value.UtcDateTime); + Assert.AreEqual(SharedAccessFilePermissions.List, permissions.SharedAccessPolicies["key1"].Permissions); + } + finally + { + share.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Set share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareSetPermissionsOverload() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + FileSharePermissions permissions = share.GetPermissions(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + share.SetPermissions(permissions); + Thread.Sleep(30 * 1000); + + CloudFileShare share2 = share.ServiceClient.GetShareReference(share.Name); + permissions = share2.GetPermissions(); + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.HasValue); + Assert.AreEqual(start, permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.Value.UtcDateTime); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.HasValue); + Assert.AreEqual(expiry, permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.Value.UtcDateTime); + Assert.AreEqual(SharedAccessFilePermissions.List, permissions.SharedAccessPolicies["key1"].Permissions); + } + finally + { + share.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Set share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareSetPermissionsAPM() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + FileSharePermissions permissions = share.GetPermissions(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + permissions.SharedAccessPolicies.Add("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = share.BeginSetPermissions(permissions, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndSetPermissions(result); + Thread.Sleep(30 * 1000); + + CloudFileShare share2 = share.ServiceClient.GetShareReference(share.Name); + result = share.BeginGetPermissions(ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + permissions = share.EndGetPermissions(result); + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.HasValue); + Assert.AreEqual(start, permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.Value.UtcDateTime); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.HasValue); + Assert.AreEqual(expiry, permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.Value.UtcDateTime); + Assert.AreEqual(SharedAccessFilePermissions.List, permissions.SharedAccessPolicies["key1"].Permissions); + } + } + finally + { + share.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Set share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareSetPermissionsAPMOverload() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + FileSharePermissions permissions = share.GetPermissions(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + permissions.SharedAccessPolicies.Add("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = share.BeginSetPermissions(permissions, null, null, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + share.EndSetPermissions(result); + Thread.Sleep(30 * 1000); + + CloudFileShare share2 = share.ServiceClient.GetShareReference(share.Name); + result = share.BeginGetPermissions(null, null, null, ar => waitHandle.Set(), null); + waitHandle.WaitOne(); + permissions = share.EndGetPermissions(result); + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.HasValue); + Assert.AreEqual(start, permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.Value.UtcDateTime); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.HasValue); + Assert.AreEqual(expiry, permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.Value.UtcDateTime); + Assert.AreEqual(SharedAccessFilePermissions.List, permissions.SharedAccessPolicies["key1"].Permissions); + } + } + finally + { + share.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Set share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareSetPermissionsTask() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + FileSharePermissions permissions = share.GetPermissionsAsync().Result; + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + permissions.SharedAccessPolicies.Add("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + share.SetPermissionsAsync(permissions).Wait(); + Thread.Sleep(30 * 1000); + + CloudFileShare share2 = share.ServiceClient.GetShareReference(share.Name); + permissions = share2.GetPermissionsAsync().Result; + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.HasValue); + Assert.AreEqual(start, permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.Value.UtcDateTime); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.HasValue); + Assert.AreEqual(expiry, permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.Value.UtcDateTime); + Assert.AreEqual(SharedAccessFilePermissions.List, permissions.SharedAccessPolicies["key1"].Permissions); + } + finally + { + share.DeleteIfExistsAsync(); + } + } + + [TestMethod] + [Description("Set share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareSetPermissionsOverloadTask() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.CreateAsync().Wait(); + + FileSharePermissions permissions = share.GetPermissionsAsync().Result; + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + share.SetPermissionsAsync(permissions).Wait(); + Thread.Sleep(30 * 1000); + + CloudFileShare share2 = share.ServiceClient.GetShareReference(share.Name); + permissions = share2.GetPermissionsAsync().Result; + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.HasValue); + Assert.AreEqual(start, permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.Value.UtcDateTime); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.HasValue); + Assert.AreEqual(expiry, permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.Value.UtcDateTime); + Assert.AreEqual(SharedAccessFilePermissions.List, permissions.SharedAccessPolicies["key1"].Permissions); + } + finally + { + share.DeleteIfExists(); + } + } +#endif + + [TestMethod] + [Description("Clear share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareClearPermissions() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + FileSharePermissions permissions = share.GetPermissions(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + share.SetPermissions(permissions); + Thread.Sleep(3 * 1000); + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + + Assert.AreEqual(true, permissions.SharedAccessPolicies.Contains(sharedAccessPolicy)); + Assert.AreEqual(true, permissions.SharedAccessPolicies.ContainsKey("key1")); + permissions.SharedAccessPolicies.Clear(); + share.SetPermissions(permissions); + Thread.Sleep(3 * 1000); + permissions = share.GetPermissions(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + } + finally + { + share.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Copy share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareCopyPermissions() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + FileSharePermissions permissions = share.GetPermissions(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + + DateTime start2 = DateTime.UtcNow; + start2 = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry2 = start.AddMinutes(30); + KeyValuePair sharedAccessPolicy2 = new KeyValuePair("key2", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start2, + SharedAccessExpiryTime = expiry2, + Permissions = SharedAccessFilePermissions.List, + }); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy2); + + KeyValuePair[] sharedAccessPolicyArray = new KeyValuePair[2]; + permissions.SharedAccessPolicies.CopyTo(sharedAccessPolicyArray, 0); + Assert.AreEqual(2, sharedAccessPolicyArray.Length); + Assert.AreEqual(sharedAccessPolicy, sharedAccessPolicyArray[0]); + Assert.AreEqual(sharedAccessPolicy2, sharedAccessPolicyArray[1]); + } + finally + { + share.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Remove share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareRemovePermissions() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + FileSharePermissions permissions = share.GetPermissions(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + + DateTime start2 = DateTime.UtcNow; + start2 = new DateTime(start2.Year, start2.Month, start2.Day, start2.Hour, start2.Minute, start2.Second, DateTimeKind.Utc); + DateTime expiry2 = start2.AddMinutes(30); + KeyValuePair sharedAccessPolicy2 = new KeyValuePair("key2", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start2, + SharedAccessExpiryTime = expiry2, + Permissions = SharedAccessFilePermissions.List, + }); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy2); + share.SetPermissions(permissions); + Assert.AreEqual(2, permissions.SharedAccessPolicies.Count); + + permissions.SharedAccessPolicies.Remove(sharedAccessPolicy2); + share.SetPermissions(permissions); + Thread.Sleep(3 * 1000); + + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + permissions = share.GetPermissions(); + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + Assert.AreEqual(sharedAccessPolicy.Key, permissions.SharedAccessPolicies.ElementAt(0).Key); + Assert.AreEqual(sharedAccessPolicy.Value.Permissions, permissions.SharedAccessPolicies.ElementAt(0).Value.Permissions); + Assert.AreEqual(sharedAccessPolicy.Value.SharedAccessStartTime, permissions.SharedAccessPolicies.ElementAt(0).Value.SharedAccessStartTime); + Assert.AreEqual(sharedAccessPolicy.Value.SharedAccessExpiryTime, permissions.SharedAccessPolicies.ElementAt(0).Value.SharedAccessExpiryTime); + + permissions.SharedAccessPolicies.Add(sharedAccessPolicy2); + share.SetPermissions(permissions); + Assert.AreEqual(2, permissions.SharedAccessPolicies.Count); + + permissions.SharedAccessPolicies.Remove("key2"); + share.SetPermissions(permissions); + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + permissions = share.GetPermissions(); + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + Assert.AreEqual(sharedAccessPolicy.Key, permissions.SharedAccessPolicies.ElementAt(0).Key); + Assert.AreEqual(sharedAccessPolicy.Value.Permissions, permissions.SharedAccessPolicies.ElementAt(0).Value.Permissions); + Assert.AreEqual(sharedAccessPolicy.Value.SharedAccessStartTime, permissions.SharedAccessPolicies.ElementAt(0).Value.SharedAccessStartTime); + Assert.AreEqual(sharedAccessPolicy.Value.SharedAccessExpiryTime, permissions.SharedAccessPolicies.ElementAt(0).Value.SharedAccessExpiryTime); + } + finally + { + share.DeleteIfExists(); + } + } + + [TestMethod] + [Description("TryGetValue for share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareTryGetValuePermissions() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + FileSharePermissions permissions = share.GetPermissions(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + + DateTime start2 = DateTime.UtcNow; + start2 = new DateTime(start2.Year, start2.Month, start2.Day, start2.Hour, start2.Minute, start2.Second, DateTimeKind.Utc); + DateTime expiry2 = start2.AddMinutes(30); + KeyValuePair sharedAccessPolicy2 = new KeyValuePair("key2", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start2, + SharedAccessExpiryTime = expiry2, + Permissions = SharedAccessFilePermissions.List, + }); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy2); + share.SetPermissions(permissions); + Thread.Sleep(3 * 1000); + Assert.AreEqual(2, permissions.SharedAccessPolicies.Count); + + permissions = share.GetPermissions(); + SharedAccessFilePolicy retrPolicy; + permissions.SharedAccessPolicies.TryGetValue("key1", out retrPolicy); + Assert.AreEqual(sharedAccessPolicy.Value.Permissions, retrPolicy.Permissions); + Assert.AreEqual(sharedAccessPolicy.Value.SharedAccessStartTime, retrPolicy.SharedAccessStartTime); + Assert.AreEqual(sharedAccessPolicy.Value.SharedAccessExpiryTime, retrPolicy.SharedAccessExpiryTime); + + SharedAccessFilePolicy retrPolicy2; + permissions.SharedAccessPolicies.TryGetValue("key2", out retrPolicy2); + Assert.AreEqual(sharedAccessPolicy2.Value.Permissions, retrPolicy2.Permissions); + Assert.AreEqual(sharedAccessPolicy2.Value.SharedAccessStartTime, retrPolicy2.SharedAccessStartTime); + Assert.AreEqual(sharedAccessPolicy2.Value.SharedAccessExpiryTime, retrPolicy2.SharedAccessExpiryTime); + } + finally + { + share.DeleteIfExists(); + } + } + + [TestMethod] + [Description("GetEnumerator for share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareGetEnumeratorPermissions() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + FileSharePermissions permissions = share.GetPermissions(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + + DateTime start2 = DateTime.UtcNow; + start2 = new DateTime(start2.Year, start2.Month, start2.Day, start2.Hour, start2.Minute, start2.Second, DateTimeKind.Utc); + DateTime expiry2 = start2.AddMinutes(30); + KeyValuePair sharedAccessPolicy2 = new KeyValuePair("key2", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start2, + SharedAccessExpiryTime = expiry2, + Permissions = SharedAccessFilePermissions.List, + }); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy2); + Assert.AreEqual(2, permissions.SharedAccessPolicies.Count); + + IEnumerator> policies = permissions.SharedAccessPolicies.GetEnumerator(); + policies.MoveNext(); + Assert.AreEqual(sharedAccessPolicy, policies.Current); + policies.MoveNext(); + Assert.AreEqual(sharedAccessPolicy2, policies.Current); + } + finally + { + share.DeleteIfExists(); + } + } + + [TestMethod] + [Description("GetValues for share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareGetValuesPermissions() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + FileSharePermissions permissions = share.GetPermissions(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + + DateTime start2 = DateTime.UtcNow; + start2 = new DateTime(start2.Year, start2.Month, start2.Day, start2.Hour, start2.Minute, start2.Second, DateTimeKind.Utc); + DateTime expiry2 = start2.AddMinutes(30); + KeyValuePair sharedAccessPolicy2 = new KeyValuePair("key2", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start2, + SharedAccessExpiryTime = expiry2, + Permissions = SharedAccessFilePermissions.List, + }); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy2); + Assert.AreEqual(2, permissions.SharedAccessPolicies.Count); + + ICollection values = permissions.SharedAccessPolicies.Values; + Assert.AreEqual(2, values.Count); + Assert.AreEqual(sharedAccessPolicy.Value, values.ElementAt(0)); + Assert.AreEqual(sharedAccessPolicy2.Value, values.ElementAt(1)); + } + finally + { + share.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Get permissions from string")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileSharePermissionsFromString() + { + SharedAccessFilePolicy policy = new SharedAccessFilePolicy(); + policy.SharedAccessStartTime = DateTime.UtcNow; + policy.SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(30); + + policy.Permissions = SharedAccessFilePolicy.PermissionsFromString("rwdl"); + Assert.AreEqual(SharedAccessFilePermissions.Read | SharedAccessFilePermissions.Write | SharedAccessFilePermissions.Delete | SharedAccessFilePermissions.List, policy.Permissions); + + policy.Permissions = SharedAccessFilePolicy.PermissionsFromString("rwl"); + Assert.AreEqual(SharedAccessFilePermissions.Read | SharedAccessFilePermissions.Write | SharedAccessFilePermissions.List, policy.Permissions); + + policy.Permissions = SharedAccessFilePolicy.PermissionsFromString("rw"); + Assert.AreEqual(SharedAccessFilePermissions.Read | SharedAccessFilePermissions.Write, policy.Permissions); + + policy.Permissions = SharedAccessFilePolicy.PermissionsFromString("rd"); + Assert.AreEqual(SharedAccessFilePermissions.Read | SharedAccessFilePermissions.Delete, policy.Permissions); + + policy.Permissions = SharedAccessFilePolicy.PermissionsFromString("wl"); + Assert.AreEqual(SharedAccessFilePermissions.Write | SharedAccessFilePermissions.List, policy.Permissions); + + policy.Permissions = SharedAccessFilePolicy.PermissionsFromString("w"); + Assert.AreEqual(SharedAccessFilePermissions.Write, policy.Permissions); + } + [TestMethod] [Description("List files")] [TestCategory(ComponentCategory.File)] @@ -1199,6 +1873,270 @@ public void FileContinuationTokenVerifyXmlWithinXml() } } + [TestMethod] + [Description("Get share stats")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareGetShareStats() + { + int megabyteInBytes = 1024 * 1024; + CloudFileShare share = GetRandomShareReference(); + + try + { + share.Create(); + CloudFileDirectory directory = share.GetRootDirectoryReference().GetDirectoryReference("directory1"); + directory.Create(); + + // should begin empty + ShareStats stats1 = share.GetStats(); + Assert.AreEqual(0, stats1.Usage); + + // should round up, upload 1 MB. + CloudFile file = directory.GetFileReference("file1"); + file.UploadFromByteArray(GetRandomBuffer(megabyteInBytes), 0, megabyteInBytes); //one mb + + ShareStats stats2 = share.GetStats(); + Assert.AreEqual(1, stats2.Usage); + } + finally + { + share.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Get share stats")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareGetShareStatsAPM() + { + int megabyteInBytes = 1024 * 1024; + CloudFileShare share = GetRandomShareReference(); + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + try + { + IAsyncResult result = share.BeginCreate( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share.EndCreate(result); + + // should begin empty + result = share.BeginGetStats( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + + ShareStats stats1 = share.EndGetStats(result); + Assert.AreEqual(0, stats1.Usage); + + // should round up, upload 1 MB and assert the usage is 1 GB. + CloudFileDirectory directory = share.GetRootDirectoryReference().GetDirectoryReference("directory1"); + CloudFile file = directory.GetFileReference("file1"); + result = directory.BeginCreate( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + directory.EndCreate(result); + result = file.BeginUploadFromByteArray( + GetRandomBuffer(megabyteInBytes), + 0, + megabyteInBytes, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + file.EndUploadFromByteArray(result); + + result = share.BeginGetStats( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + + ShareStats stats2 = share.EndGetStats(result); + Assert.AreEqual(1, stats2.Usage); + + } + finally + { + IAsyncResult result = share.BeginDeleteIfExists( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share.EndDeleteIfExists(result); + } + } + } + +#if TASK + [TestMethod] + [Description("Get service stats")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareGetShareStatsTask() + { + int megabyteInBytes = 1024 * 1024; + CloudFileShare share = GetRandomShareReference(); + + try + { + share.CreateAsync().Wait(); + + // should begin empty + Task statsTask1 = share.GetStatsAsync(); + statsTask1.Wait(); + ShareStats stats1 = statsTask1.Result; + Assert.AreEqual(0, stats1.Usage); + + // should round up, upload 1 MB and assert the usage is 1 GB. + CloudFileDirectory directory = share.GetRootDirectoryReference().GetDirectoryReference("directory1"); + CloudFile file = directory.GetFileReference("file1"); + directory.CreateAsync().Wait(); + file.UploadFromByteArrayAsync(GetRandomBuffer(megabyteInBytes), 0, megabyteInBytes).Wait(); //one mb + + Task statsTask2 = share.GetStatsAsync(); + statsTask2.Wait(); + ShareStats stats2 = statsTask2.Result; + Assert.AreEqual(1, stats2.Usage); + } + finally + { + share.DeleteIfExistsAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Verify setting the properties of a share")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareSetProperties() + { + string shareName = GetRandomShareName(); + CloudFileClient client = GenerateCloudFileClient(); + + try + { + CloudFileShare share1 = client.GetShareReference(shareName); + share1.Create(); + + share1.FetchAttributes(); + Assert.AreEqual(5120, share1.Properties.Quota); + + share1.Properties.Quota = 8; + share1.SetProperties(); + + CloudFileShare share2 = client.GetShareReference(shareName); + share2.FetchAttributes(); + Assert.AreEqual(8, share2.Properties.Quota); + } + finally + { + client.GetShareReference(shareName).DeleteIfExists(); + } + } + + [TestMethod] + [Description("Verify setting the properties of a share")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareSetPropertiesAPM() + { + string shareName = GetRandomShareName(); + CloudFileClient client = GenerateCloudFileClient(); + + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + try + { + CloudFileShare share1 = client.GetShareReference(shareName); + IAsyncResult result = share1.BeginCreate( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share1.EndCreate(result); + + result = share1.BeginFetchAttributes( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share1.EndFetchAttributes(result); + + Assert.AreEqual(5120, share1.Properties.Quota); + + share1.Properties.Quota = 8; + result = share1.BeginSetProperties( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share1.EndSetProperties(result); + + CloudFileShare share2 = client.GetShareReference(shareName); + result = share2.BeginFetchAttributes( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + share2.EndFetchAttributes(result); + + Assert.AreEqual(8, share2.Properties.Quota); + } + finally + { + IAsyncResult result = client.GetShareReference(shareName).BeginDeleteIfExists( + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + client.GetShareReference(shareName).EndDeleteIfExists(result); + } + } + } + +#if TASK + [TestMethod] + [Description("Verify setting the properties of a share")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareSetPropertiesTask() + { + string shareName = GetRandomShareName(); + CloudFileClient client = GenerateCloudFileClient(); + + try + { + CloudFileShare share1 = client.GetShareReference(shareName); + share1.CreateAsync().Wait(); + + share1.FetchAttributesAsync().Wait(); + Assert.AreEqual(5120, share1.Properties.Quota); + + share1.Properties.Quota = 8; + share1.SetPropertiesAsync().Wait(); + + CloudFileShare share2 = client.GetShareReference(shareName); + share2.FetchAttributesAsync().Wait(); + Assert.AreEqual(8, share2.Properties.Quota); + } + finally + { + client.GetShareReference(shareName).DeleteIfExistsAsync().Wait(); + } + } +#endif + /* [TestMethod] [Description("Test conditional access on a share")] diff --git a/Test/ClassLibraryCommon/File/CloudFileTest.cs b/Test/ClassLibraryCommon/File/CloudFileTest.cs index 344b0d971..dd1d340dc 100644 --- a/Test/ClassLibraryCommon/File/CloudFileTest.cs +++ b/Test/ClassLibraryCommon/File/CloudFileTest.cs @@ -2486,5 +2486,47 @@ public void IAsyncWaitHandleTest() share.DeleteIfExists(); } } + + [TestMethod] + [Description("Try operations with an invalid Sas")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileInvalidSas() + { + // Sas token creds. + string token = "?sp=abcde&sig=1"; + StorageCredentials creds = new StorageCredentials(token); + Assert.IsTrue(creds.IsSAS); + + // Client with shared key access. + CloudFileClient fileClient = GenerateCloudFileClient(); + CloudFileShare share = fileClient.GetShareReference(GetRandomShareName()); + try + { + share.Create(); + + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = SharedAccessFilePermissions.Read | SharedAccessFilePermissions.Write, + }; + string sasToken = share.GetSharedAccessSignature(policy); + + string fileUri = share.Uri.AbsoluteUri + "/file1" + sasToken; + TestHelper.ExpectedException( + () => new CloudFile(new Uri(fileUri), share.ServiceClient.Credentials), + "Try to use SAS creds in Uri on a shared key client"); + + CloudFile file = share.GetRootDirectoryReference().GetFileReference("file1"); + file.UploadFromStream(new MemoryStream(GetRandomBuffer(10))); + } + finally + { + share.DeleteIfExists(); + } + } } } diff --git a/Test/ClassLibraryCommon/File/CopyFileTest.cs b/Test/ClassLibraryCommon/File/CopyFileTest.cs new file mode 100644 index 000000000..47716e7a8 --- /dev/null +++ b/Test/ClassLibraryCommon/File/CopyFileTest.cs @@ -0,0 +1,440 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.WindowsAzure.Storage.Auth; +using Microsoft.WindowsAzure.Storage.Blob; +using System; +using System.Net; +using System.Text; +using System.Threading; + +namespace Microsoft.WindowsAzure.Storage.File +{ + [TestClass] + public class CopyFileTest : FileTestBase + { + // + // Use TestInitialize to run code before running each test + [TestInitialize()] + public void MyTestInitialize() + { + if (TestBase.FileBufferManager != null) + { + TestBase.FileBufferManager.OutstandingBufferCount = 0; + } + } + // + // Use TestCleanup to run code after each test has run + [TestCleanup()] + public void MyTestCleanup() + { + if (TestBase.FileBufferManager != null) + { + Assert.AreEqual(0, TestBase.FileBufferManager.OutstandingBufferCount); + } + } + + [TestMethod] + [Description("CopyFromFile with Unicode source file")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CopyFileUsingUnicodeFileName() + { + string _unicodeFileName = "繁体字14a6c"; + string _nonUnicodeFileName = "sample_file"; + + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + CloudFile fileUnicodeSource = share.GetRootDirectoryReference().GetFileReference(_unicodeFileName); + string data = "Test content"; + UploadText(fileUnicodeSource, data, Encoding.UTF8); + CloudFile fileAsciiSource = share.GetRootDirectoryReference().GetFileReference(_nonUnicodeFileName); + UploadText(fileAsciiSource, data, Encoding.UTF8); + + //Copy files over + CloudFile fileAsciiDest = share.GetRootDirectoryReference().GetFileReference(_nonUnicodeFileName + "_copy"); + string copyId = fileAsciiDest.StartCopy(TestHelper.Defiddler(fileAsciiSource)); + WaitForCopy(fileAsciiDest); + + CloudFile fileUnicodeDest = share.GetRootDirectoryReference().GetFileReference(_unicodeFileName + "_copy"); + copyId = fileUnicodeDest.StartCopy(TestHelper.Defiddler(fileUnicodeSource)); + WaitForCopy(fileUnicodeDest); + + Assert.AreEqual(CopyStatus.Success, fileUnicodeDest.CopyState.Status); + Assert.AreEqual(fileUnicodeSource.Uri.AbsolutePath, fileUnicodeDest.CopyState.Source.AbsolutePath); + Assert.AreEqual(data.Length, fileUnicodeDest.CopyState.TotalBytes); + Assert.AreEqual(data.Length, fileUnicodeDest.CopyState.BytesCopied); + Assert.AreEqual(copyId, fileUnicodeDest.CopyState.CopyId); + Assert.IsTrue(fileUnicodeDest.CopyState.CompletionTime > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1))); + } + finally + { + share.DeleteIfExists(); + } + } + + private static void CloudFileCopy(bool sourceIsSas, bool destinationIsSas) + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + // Create Source on server + CloudFile source = share.GetRootDirectoryReference().GetFileReference("source"); + + string data = "String data"; + UploadText(source, data, Encoding.UTF8); + + source.Metadata["Test"] = "value"; + source.SetMetadata(); + + // Create Destination on server + CloudFile destination = share.GetRootDirectoryReference().GetFileReference("destination"); + destination.Create(1); + + CloudFile copySource = source; + CloudFile copyDestination = destination; + + if (sourceIsSas) + { + // Source SAS must have read permissions + SharedAccessFilePermissions permissions = SharedAccessFilePermissions.Read; + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = permissions, + }; + string sasToken = source.GetSharedAccessSignature(policy); + + // Get source + StorageCredentials credentials = new StorageCredentials(sasToken); + copySource = new CloudFile(credentials.TransformUri(source.Uri)); + } + + if (destinationIsSas) + { + Assert.IsTrue(sourceIsSas); + + // Destination SAS must have write permissions + SharedAccessFilePermissions permissions = SharedAccessFilePermissions.Write; + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = permissions, + }; + string sasToken = destination.GetSharedAccessSignature(policy); + + // Get destination + StorageCredentials credentials = new StorageCredentials(sasToken); + copyDestination = new CloudFile(credentials.TransformUri(destination.Uri)); + } + + // Start copy and wait for completion + string copyId = copyDestination.StartCopy(TestHelper.Defiddler(copySource)); + WaitForCopy(destination); + + // Check original file references for equality + Assert.AreEqual(CopyStatus.Success, destination.CopyState.Status); + Assert.AreEqual(source.Uri.AbsolutePath, destination.CopyState.Source.AbsolutePath); + Assert.AreEqual(data.Length, destination.CopyState.TotalBytes); + Assert.AreEqual(data.Length, destination.CopyState.BytesCopied); + Assert.AreEqual(copyId, destination.CopyState.CopyId); + Assert.IsTrue(destination.CopyState.CompletionTime > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1))); + + if (!destinationIsSas) + { + // Abort Copy is not supported for SAS destination + TestHelper.ExpectedException( + () => copyDestination.AbortCopy(copyId), + "Aborting a copy operation after completion should fail", + HttpStatusCode.Conflict, + "NoPendingCopyOperation"); + } + + source.FetchAttributes(); + Assert.IsNotNull(destination.Properties.ETag); + Assert.AreNotEqual(source.Properties.ETag, destination.Properties.ETag); + Assert.IsTrue(destination.Properties.LastModified > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1))); + + string copyData = DownloadText(destination, Encoding.UTF8); + Assert.AreEqual(data, copyData, "Data inside copy of file not equal."); + + destination.FetchAttributes(); + FileProperties prop1 = destination.Properties; + FileProperties prop2 = source.Properties; + + Assert.AreEqual(prop1.CacheControl, prop2.CacheControl); + Assert.AreEqual(prop1.ContentEncoding, prop2.ContentEncoding); + Assert.AreEqual(prop1.ContentLanguage, prop2.ContentLanguage); + Assert.AreEqual(prop1.ContentMD5, prop2.ContentMD5); + Assert.AreEqual(prop1.ContentType, prop2.ContentType); + + Assert.AreEqual("value", destination.Metadata["Test"], false, "Copied metadata not same"); + + destination.Delete(); + source.Delete(); + } + finally + { + share.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Copy a file and then verify its contents, properties, and metadata")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileCopySasToSasTest() + { + CloudFileCopy(true, true); + } + + [TestMethod] + [Description("Copy a file and then verify its contents, properties, and metadata")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileCopyFromSasTest() + { + CloudFileCopy(true, false); + } + + [TestMethod] + [Description("Copy a file and then verify its contents, properties, and metadata")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileCopyTest() + { + CloudFileCopy(false, false); + } + + [TestMethod] + [Description("Copy a file and then verify its contents, properties, and metadata")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileCopyTestAPM() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + CloudFile source = share.GetRootDirectoryReference().GetFileReference("source"); + + string data = "String data"; + UploadText(source, data, Encoding.UTF8); + + source.Metadata["Test"] = "value"; + source.SetMetadata(); + + CloudFile copy = share.GetRootDirectoryReference().GetFileReference("copy"); + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + IAsyncResult result = copy.BeginStartCopy(TestHelper.Defiddler(source), + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + string copyId = copy.EndStartCopy(result); + WaitForCopy(copy); + Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); + Assert.AreEqual(source.Uri.AbsolutePath, copy.CopyState.Source.AbsolutePath); + Assert.AreEqual(data.Length, copy.CopyState.TotalBytes); + Assert.AreEqual(data.Length, copy.CopyState.BytesCopied); + Assert.AreEqual(copyId, copy.CopyState.CopyId); + Assert.IsTrue(copy.CopyState.CompletionTime > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1))); + + result = copy.BeginAbortCopy(copyId, + ar => waitHandle.Set(), + null); + waitHandle.WaitOne(); + TestHelper.ExpectedException( + () => copy.EndAbortCopy(result), + "Aborting a copy operation after completion should fail", + HttpStatusCode.Conflict, + "NoPendingCopyOperation"); + } + + source.FetchAttributes(); + Assert.IsNotNull(copy.Properties.ETag); + Assert.AreNotEqual(source.Properties.ETag, copy.Properties.ETag); + Assert.IsTrue(copy.Properties.LastModified > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1))); + + string copyData = DownloadText(copy, Encoding.UTF8); + Assert.AreEqual(data, copyData, "Data inside copy of file not similar"); + + copy.FetchAttributes(); + FileProperties prop1 = copy.Properties; + FileProperties prop2 = source.Properties; + + Assert.AreEqual(prop1.CacheControl, prop2.CacheControl); + Assert.AreEqual(prop1.ContentEncoding, prop2.ContentEncoding); + Assert.AreEqual(prop1.ContentDisposition, prop2.ContentDisposition); + Assert.AreEqual(prop1.ContentLanguage, prop2.ContentLanguage); + Assert.AreEqual(prop1.ContentMD5, prop2.ContentMD5); + Assert.AreEqual(prop1.ContentType, prop2.ContentType); + + Assert.AreEqual("value", copy.Metadata["Test"], false, "Copied metadata not same"); + + copy.Delete(); + } + finally + { + share.DeleteIfExists(); + } + } + +#if TASK + [TestMethod] + [Description("Copy a file and then verify its contents, properties, and metadata")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileCopyTestTask() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.CreateAsync().Wait(); + + CloudFile source = share.GetRootDirectoryReference().GetFileReference("source"); + + string data = "String data"; + UploadTextTask(source, data, Encoding.UTF8); + + source.Metadata["Test"] = "value"; + source.SetMetadataAsync().Wait(); + + CloudFile copy = share.GetRootDirectoryReference().GetFileReference("copy"); + string copyId = copy.StartCopyAsync(TestHelper.Defiddler(source)).Result; + WaitForCopyTask(copy); + Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); + Assert.AreEqual(source.Uri.AbsolutePath, copy.CopyState.Source.AbsolutePath); + Assert.AreEqual(data.Length, copy.CopyState.TotalBytes); + Assert.AreEqual(data.Length, copy.CopyState.BytesCopied); + Assert.AreEqual(copyId, copy.CopyState.CopyId); + Assert.IsTrue(copy.CopyState.CompletionTime > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1))); + + TestHelper.ExpectedExceptionTask( + copy.AbortCopyAsync(copyId), + "Aborting a copy operation after completion should fail", + HttpStatusCode.Conflict, + "NoPendingCopyOperation"); + + source.FetchAttributesAsync().Wait(); + Assert.IsNotNull(copy.Properties.ETag); + Assert.AreNotEqual(source.Properties.ETag, copy.Properties.ETag); + Assert.IsTrue(copy.Properties.LastModified > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1))); + + string copyData = DownloadTextTask(copy, Encoding.UTF8); + Assert.AreEqual(data, copyData, "Data inside copy of file not similar"); + + copy.FetchAttributesAsync().Wait(); + FileProperties prop1 = copy.Properties; + FileProperties prop2 = source.Properties; + + Assert.AreEqual(prop1.CacheControl, prop2.CacheControl); + Assert.AreEqual(prop1.ContentEncoding, prop2.ContentEncoding); + Assert.AreEqual(prop1.ContentLanguage, prop2.ContentLanguage); + Assert.AreEqual(prop1.ContentMD5, prop2.ContentMD5); + Assert.AreEqual(prop1.ContentType, prop2.ContentType); + + Assert.AreEqual("value", copy.Metadata["Test"], false, "Copied metadata not same"); + + copy.DeleteAsync().Wait(); + } + finally + { + share.DeleteIfExistsAsync().Wait(); + } + } +#endif + + [TestMethod] + [Description("Copy a file and override metadata during copy")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileCopyTestWithMetadataOverride() + { + CloudFileShare share = GetRandomShareReference(); + try + { + share.Create(); + + CloudFile source = share.GetRootDirectoryReference().GetFileReference("source"); + + string data = "String data"; + UploadText(source, data, Encoding.UTF8); + + source.Metadata["Test"] = "value"; + source.SetMetadata(); + + CloudFile copy = share.GetRootDirectoryReference().GetFileReference("copy"); + copy.Metadata["Test2"] = "value2"; + string copyId = copy.StartCopy(TestHelper.Defiddler(source)); + WaitForCopy(copy); + Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); + Assert.AreEqual(source.Uri.AbsolutePath, copy.CopyState.Source.AbsolutePath); + Assert.AreEqual(data.Length, copy.CopyState.TotalBytes); + Assert.AreEqual(data.Length, copy.CopyState.BytesCopied); + Assert.AreEqual(copyId, copy.CopyState.CopyId); + Assert.IsTrue(copy.CopyState.CompletionTime > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1))); + + string copyData = DownloadText(copy, Encoding.UTF8); + Assert.AreEqual(data, copyData, "Data inside copy of file not similar"); + + copy.FetchAttributes(); + source.FetchAttributes(); + FileProperties prop1 = copy.Properties; + FileProperties prop2 = source.Properties; + + Assert.AreEqual(prop1.CacheControl, prop2.CacheControl); + Assert.AreEqual(prop1.ContentEncoding, prop2.ContentEncoding); + Assert.AreEqual(prop1.ContentDisposition, prop2.ContentDisposition); + Assert.AreEqual(prop1.ContentLanguage, prop2.ContentLanguage); + Assert.AreEqual(prop1.ContentMD5, prop2.ContentMD5); + Assert.AreEqual(prop1.ContentType, prop2.ContentType); + + Assert.AreEqual("value2", copy.Metadata["Test2"], false, "Copied metadata not same"); + Assert.IsFalse(copy.Metadata.ContainsKey("Test"), "Source Metadata should not appear in destination file"); + + copy.Delete(); + } + finally + { + share.DeleteIfExists(); + } + } + } +} diff --git a/Test/ClassLibraryCommon/File/FileAnalyticsUnitTests.cs b/Test/ClassLibraryCommon/File/FileAnalyticsUnitTests.cs new file mode 100644 index 000000000..5336e6259 --- /dev/null +++ b/Test/ClassLibraryCommon/File/FileAnalyticsUnitTests.cs @@ -0,0 +1,530 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File +{ + using System; + using System.Collections.Generic; + using System.Net; + using System.Threading; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using Microsoft.WindowsAzure.Storage.File.Protocol; + + [TestClass] + public class FileAnalyticsUnitTests : TestBase + { + #region Locals + Ctors + + /// + /// Gets or sets the test context which provides + /// information about and functionality for the current test run. + /// + public TestContext TestContext { get; set; } + + private static CloudFileClient client; + private static FileServiceProperties props; + private static FileServiceProperties startProperties = null; + #endregion + + #region Additional test attributes + + /// + /// You can use the following additional attributes as you write your tests: + /// Use ClassInitialize to run code before running the first test in the class + /// + [ClassInitialize] + public static void MyClassInitialize(TestContext testContext) + { + client = GenerateCloudFileClient(); + startProperties = client.GetServiceProperties(); + } + + /// + /// Use ClassCleanup to run code after all tests in a class have run + /// + [ClassCleanup] + public static void MyClassCleanup() + { + client.SetServiceProperties(startProperties); + } + + /// + /// Use TestInitialize to run code before running each test + /// + [TestInitialize] + public void MyTestInitialize() + { + props = DefaultServiceProperties(); + + if (TestBase.FileBufferManager != null) + { + TestBase.FileBufferManager.OutstandingBufferCount = 0; + } + } + + /// + /// Use TestCleanup to run code after each test has run + /// + [TestCleanup] + public void MyTestCleanup() + { + if (TestBase.FileBufferManager != null) + { + Assert.AreEqual(0, TestBase.FileBufferManager.OutstandingBufferCount); + } + } + + #endregion + + #region Analytics RoundTrip + + #region Sync + + [TestMethod] + [Description("Test Analytics Round Trip Sync")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileTestAnalyticsRoundTripSync() + { + props.Cors.CorsRules.Add( + new CorsRule() + { + AllowedOrigins = new List() { "www.ab.com", "www.bc.com" }, + AllowedMethods = CorsHttpMethods.Get | CorsHttpMethods.Put, + MaxAgeInSeconds = 500, + ExposedHeaders = + new List() + { + "x-ms-meta-data*", + "x-ms-meta-source*", + "x-ms-meta-abc", + "x-ms-meta-bcd" + }, + AllowedHeaders = + new List() + { + "x-ms-meta-data*", + "x-ms-meta-target*", + "x-ms-meta-xyz", + "x-ms-meta-foo" + } + }); + + client.SetServiceProperties(props); + + TestHelper.AssertFileServicePropertiesAreEqual(props, client.GetServiceProperties()); + } + + #endregion + + #region APM + + [TestMethod] + [Description("Test Analytics Round Trip APM")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileTestAnalyticsRoundTripAPM() + { + props.Cors.CorsRules.Add( + new CorsRule() + { + AllowedOrigins = new List() { "www.ab.com", "www.bc.com" }, + AllowedMethods = CorsHttpMethods.Get | CorsHttpMethods.Put, + MaxAgeInSeconds = 500, + ExposedHeaders = + new List() + { + "x-ms-meta-data*", + "x-ms-meta-source*", + "x-ms-meta-abc", + "x-ms-meta-bcd" + }, + AllowedHeaders = + new List() + { + "x-ms-meta-data*", + "x-ms-meta-target*", + "x-ms-meta-xyz", + "x-ms-meta-foo" + } + }); + + using (ManualResetEvent evt = new ManualResetEvent(false)) + { + IAsyncResult result = null; + client.BeginSetServiceProperties(props, (res) => + { + result = res; + evt.Set(); + }, null); + evt.WaitOne(); + + client.EndSetServiceProperties(result); + } + + FileServiceProperties retrievedProps = null; + using (ManualResetEvent evt = new ManualResetEvent(false)) + { + IAsyncResult result = null; + client.BeginGetServiceProperties((res) => + { + result = res; + evt.Set(); + }, null); + evt.WaitOne(); + + retrievedProps = client.EndGetServiceProperties(result); + } + + TestHelper.AssertFileServicePropertiesAreEqual(props, retrievedProps); + } + + #endregion + + #endregion + + #region Analytics Permutations + + [TestMethod] + [Description("Test Analytics Disable Service Properties")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileTestAnalyticsDisable() + { + // These are set to defaults in the test initialization + client.SetServiceProperties(props); + + // Check that the default service properties set in the Test Initialization were uploaded correctly + TestHelper.AssertFileServicePropertiesAreEqual(props, client.GetServiceProperties()); + } + + [TestMethod] + [Description("Test CORS with different rules.")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileTestValidCorsRules() + { + CorsRule ruleMinRequired = new CorsRule() + { + AllowedOrigins = new List() { "www.xyz.com" }, + AllowedMethods = CorsHttpMethods.Get + }; + + CorsRule ruleBasic = new CorsRule() + { + AllowedOrigins = new List() { "www.ab.com", "www.bc.com" }, + AllowedMethods = CorsHttpMethods.Get | CorsHttpMethods.Put, + MaxAgeInSeconds = 500, + ExposedHeaders = + new List() + { + "x-ms-meta-data*", + "x-ms-meta-source*", + "x-ms-meta-abc", + "x-ms-meta-bcd" + }, + AllowedHeaders = + new List() + { + "x-ms-meta-data*", + "x-ms-meta-target*", + "x-ms-meta-xyz", + "x-ms-meta-foo" + } + }; + + CorsRule ruleAllMethods = new CorsRule() + { + AllowedOrigins = new List() { "www.xyz.com" }, + AllowedMethods = + CorsHttpMethods.Put | CorsHttpMethods.Trace + | CorsHttpMethods.Connect | CorsHttpMethods.Delete + | CorsHttpMethods.Get | CorsHttpMethods.Head + | CorsHttpMethods.Options | CorsHttpMethods.Post + | CorsHttpMethods.Merge + }; + + CorsRule ruleSingleExposedHeader = new CorsRule() + { + AllowedOrigins = new List() { "www.ab.com" }, + AllowedMethods = CorsHttpMethods.Get, + ExposedHeaders = new List() { "x-ms-meta-bcd" }, + }; + + CorsRule ruleSingleExposedPrefixHeader = new CorsRule() + { + AllowedOrigins = + new List() { "www.ab.com" }, + AllowedMethods = CorsHttpMethods.Get, + ExposedHeaders = + new List() { "x-ms-meta-data*" }, + }; + + CorsRule ruleSingleAllowedHeader = new CorsRule() + { + AllowedOrigins = new List() { "www.ab.com" }, + AllowedMethods = CorsHttpMethods.Get, + AllowedHeaders = new List() { "x-ms-meta-xyz", }, + }; + + CorsRule ruleSingleAllowedPrefixHeader = new CorsRule() + { + AllowedOrigins = + new List() { "www.ab.com" }, + AllowedMethods = CorsHttpMethods.Get, + AllowedHeaders = + new List() { "x-ms-meta-target*" }, + }; + + CorsRule ruleAllowAll = new CorsRule() + { + AllowedOrigins = new List() { "*" }, + AllowedMethods = CorsHttpMethods.Get, + AllowedHeaders = new List() { "*" }, + ExposedHeaders = new List() { "*" } + }; + + CloudFileClient client = GenerateCloudFileClient(); + + this.TestCorsRules(client, new List() { ruleBasic }); + + this.TestCorsRules(client, new List() { ruleMinRequired }); + + this.TestCorsRules(client, new List() { ruleAllMethods }); + + this.TestCorsRules(client, new List() { ruleSingleExposedHeader }); + + this.TestCorsRules(client, new List() { ruleSingleExposedPrefixHeader }); + + this.TestCorsRules(client, new List() { ruleSingleAllowedHeader }); + + this.TestCorsRules(client, new List() { ruleSingleAllowedPrefixHeader }); + + this.TestCorsRules(client, new List() { ruleAllowAll }); + + // Empty rule set should delete all rules + this.TestCorsRules(client, new List() { }); + + // Test duplicate rules + this.TestCorsRules(client, new List() { ruleBasic, ruleBasic }); + + // Test max number of rules (five) + this.TestCorsRules( + client, + new List() + { + ruleBasic, + ruleMinRequired, + ruleAllMethods, + ruleSingleExposedHeader, + ruleSingleExposedPrefixHeader + }); + + + // Test max number of rules + 1 (six) + TestHelper.ExpectedException( + () => + this.TestCorsRules( + client, + new List() + { + ruleBasic, + ruleMinRequired, + ruleAllMethods, + ruleSingleExposedHeader, + ruleSingleExposedPrefixHeader, + ruleSingleAllowedHeader + }), + "Services are limited to a maximum of five CORS rules.", + HttpStatusCode.BadRequest, + "InvalidXmlDocument"); + } + + [TestMethod] + [Description("Test CORS with invalid values.")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileTestCorsExpectedExceptions() + { + CorsRule ruleEmpty = new CorsRule(); + + CorsRule ruleInvalidMaxAge = new CorsRule() + { + AllowedOrigins = new List() { "www.xyz.com" }, + AllowedMethods = CorsHttpMethods.Get, + MaxAgeInSeconds = -1 + }; + + CloudFileClient client = GenerateCloudFileClient(); + + TestHelper.ExpectedException( + () => this.TestCorsRules(client, new List() { ruleEmpty }), "Empty CORS Rules are not supported."); + + TestHelper.ExpectedException( + () => this.TestCorsRules(client, new List() { ruleInvalidMaxAge }), + "MaxAgeInSeconds cannot have a value < 0."); + } + + [TestMethod] + [Description("Test CORS with a valid and invalid number of origin values sent to server.")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileTestCorsMaxOrigins() + { + CorsRule ruleManyOrigins = new CorsRule() { AllowedMethods = CorsHttpMethods.Get, }; + + // Add maximum number of allowed origins + for (int i = 0; i < 64; i++) + { + ruleManyOrigins.AllowedOrigins.Add("www.xyz" + i + ".com"); + } + + CloudFileClient client = GenerateCloudFileClient(); + + this.TestCorsRules(client, new List() { ruleManyOrigins }); + + ruleManyOrigins.AllowedOrigins.Add("www.xyz64.com"); + + TestHelper.ExpectedException( + () => this.TestCorsRules(client, new List() { ruleManyOrigins }), + "A maximum of 64 origins are allowed.", + HttpStatusCode.BadRequest, + "InvalidXmlNodeValue"); + } + + [TestMethod] + [Description("Test CORS with a valid and invalid number of header values sent to server.")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric)] + [TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileTestCorsMaxHeaders() + { + CorsRule ruleManyHeaders = new CorsRule() + { + AllowedOrigins = new List() { "www.xyz.com" }, + AllowedMethods = CorsHttpMethods.Get, + AllowedHeaders = + new List() + { + "x-ms-meta-target*", + "x-ms-meta-other*" + }, + ExposedHeaders = + new List() + { + "x-ms-meta-data*", + "x-ms-meta-source*" + } + }; + + // Add maximum number of non-prefixed headers + for (int i = 0; i < 64; i++) + { + ruleManyHeaders.ExposedHeaders.Add("x-ms-meta-" + i); + ruleManyHeaders.AllowedHeaders.Add("x-ms-meta-" + i); + } + + CloudFileClient client = GenerateCloudFileClient(); + + this.TestCorsRules(client, new List() { ruleManyHeaders }); + + // Test with too many Exposed Headers (65) + ruleManyHeaders.ExposedHeaders.Add("x-ms-meta-toomany"); + + TestHelper.ExpectedException( + () => this.TestCorsRules(client, new List() { ruleManyHeaders }), + "A maximum of 64 literal exposed headers are allowed.", + HttpStatusCode.BadRequest, + "InvalidXmlNodeValue"); + + ruleManyHeaders.ExposedHeaders.Remove("x-ms-meta-toomany"); + + // Test with too many Allowed Headers (65) + ruleManyHeaders.AllowedHeaders.Add("x-ms-meta-toomany"); + + TestHelper.ExpectedException( + () => this.TestCorsRules(client, new List() { ruleManyHeaders }), + "A maximum of 64 literal allowed headers are allowed.", + HttpStatusCode.BadRequest, + "InvalidXmlNodeValue"); + + ruleManyHeaders.AllowedHeaders.Remove("x-ms-meta-toomany"); + + // Test with too many Exposed Prefixed Headers (three) + ruleManyHeaders.ExposedHeaders.Add("x-ms-meta-toomany*"); + + TestHelper.ExpectedException( + () => this.TestCorsRules(client, new List() { ruleManyHeaders }), + "A maximum of two prefixed exposed headers are allowed.", + HttpStatusCode.BadRequest, + "InvalidXmlNodeValue"); + + ruleManyHeaders.ExposedHeaders.Remove("x-ms-meta-toomany*"); + + // Test with too many Allowed Prefixed Headers (three) + ruleManyHeaders.AllowedHeaders.Add("x-ms-meta-toomany*"); + + TestHelper.ExpectedException( + () => this.TestCorsRules(client, new List() { ruleManyHeaders }), + "A maximum of two prefixed allowed headers are allowed.", + HttpStatusCode.BadRequest, + "InvalidXmlNodeValue"); + + ruleManyHeaders.AllowedHeaders.Remove("x-ms-meta-toomany*"); + } + #endregion + + #region Test Helpers + private void TestCorsRules(CloudFileClient client, IList corsProps) + { + props.Cors.CorsRules.Clear(); + + foreach (CorsRule rule in corsProps) + { + props.Cors.CorsRules.Add(rule); + } + + client.SetServiceProperties(props); + TestHelper.AssertFileServicePropertiesAreEqual(props, client.GetServiceProperties()); + } + + private static FileServiceProperties DefaultServiceProperties() + { + FileServiceProperties props = new FileServiceProperties(); + + props.Cors.CorsRules = new List(); + + return props; + } + #endregion + } +} diff --git a/Test/ClassLibraryCommon/File/FileSASTests.cs b/Test/ClassLibraryCommon/File/FileSASTests.cs new file mode 100644 index 000000000..8f1ba215e --- /dev/null +++ b/Test/ClassLibraryCommon/File/FileSASTests.cs @@ -0,0 +1,315 @@ +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.WindowsAzure.Storage.Auth; +using Microsoft.WindowsAzure.Storage.Shared.Protocol; +using Microsoft.WindowsAzure.Storage.Core; +using System; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; + +namespace Microsoft.WindowsAzure.Storage.File +{ + [TestClass] + public class FileSASTests : FileTestBase + { + private CloudFileShare testShare; + + [TestInitialize] + public void TestInitialize() + { + this.testShare = GetRandomShareReference(); + this.testShare.Create(); + + if (TestBase.FileBufferManager != null) + { + TestBase.FileBufferManager.OutstandingBufferCount = 0; + } + } + + [TestCleanup] + public void TestCleanup() + { + this.testShare.Delete(); + this.testShare = null; + if (TestBase.FileBufferManager != null) + { + Assert.AreEqual(0, TestBase.FileBufferManager.OutstandingBufferCount); + } + } + + private static void TestAccess(string sasToken, SharedAccessFilePermissions permissions, SharedAccessFileHeaders headers, CloudFileShare share, CloudFile file) + { + StorageCredentials credentials = string.IsNullOrEmpty(sasToken) ? + new StorageCredentials() : + new StorageCredentials(sasToken); + + if (share != null) + { + share = new CloudFileShare(credentials.TransformUri(share.Uri)); + file = share.GetRootDirectoryReference().GetFileReference(file.Name); + } + else + { + file = new CloudFile(credentials.TransformUri(file.Uri)); + } + + if (share != null) + { + if ((permissions & SharedAccessFilePermissions.List) == SharedAccessFilePermissions.List) + { + share.GetRootDirectoryReference().ListFilesAndDirectories().ToArray(); + } + else + { + TestHelper.ExpectedException( + () => share.GetRootDirectoryReference().ListFilesAndDirectories().ToArray(), + "List files while SAS does not allow for listing", + HttpStatusCode.NotFound); + } + } + + if ((permissions & SharedAccessFilePermissions.Read) == SharedAccessFilePermissions.Read) + { + file.FetchAttributes(); + + // Test headers + if (headers != null) + { + if (headers.CacheControl != null) + { + Assert.AreEqual(headers.CacheControl, file.Properties.CacheControl); + } + + if (headers.ContentDisposition != null) + { + Assert.AreEqual(headers.ContentDisposition, file.Properties.ContentDisposition); + } + + if (headers.ContentEncoding != null) + { + Assert.AreEqual(headers.ContentEncoding, file.Properties.ContentEncoding); + } + + if (headers.ContentLanguage != null) + { + Assert.AreEqual(headers.ContentLanguage, file.Properties.ContentLanguage); + } + + if (headers.ContentType != null) + { + Assert.AreEqual(headers.ContentType, file.Properties.ContentType); + } + } + } + else + { + TestHelper.ExpectedException( + () => file.FetchAttributes(), + "Fetch file attributes while SAS does not allow for reading", + HttpStatusCode.NotFound); + } + + if ((permissions & SharedAccessFilePermissions.Write) == SharedAccessFilePermissions.Write) + { + file.SetMetadata(); + } + else + { + TestHelper.ExpectedException( + () => file.SetMetadata(), + "Set file metadata while SAS does not allow for writing", + HttpStatusCode.NotFound); + } + + if ((permissions & SharedAccessFilePermissions.Delete) == SharedAccessFilePermissions.Delete) + { + file.Delete(); + } + else + { + TestHelper.ExpectedException( + () => file.Delete(), + "Delete file while SAS does not allow for deleting", + HttpStatusCode.NotFound); + } + } + + private static void TestFileSAS(CloudFile testFile, SharedAccessFilePermissions permissions, SharedAccessFileHeaders headers) + { + UploadText(testFile, "file", Encoding.UTF8); + + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = permissions, + }; + + string sasToken = testFile.GetSharedAccessSignature(policy, headers, null); + TestAccess(sasToken, permissions, headers, null, testFile); + } + + [TestMethod] + [Description("Test updateSASToken")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareUpdateSASToken() + { + // Create a policy with read/write access and get SAS. + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = SharedAccessFilePermissions.Read | SharedAccessFilePermissions.Write, + }; + string sasToken = this.testShare.GetSharedAccessSignature(policy); + //Thread.Sleep(35000); + CloudFile testFile = this.testShare.GetRootDirectoryReference().GetFileReference("file"); + UploadText(testFile, "file", Encoding.UTF8); + TestAccess(sasToken, SharedAccessFilePermissions.Read | SharedAccessFilePermissions.Write, null, this.testShare, testFile); + + StorageCredentials creds = new StorageCredentials(sasToken); + + // Change the policy to only read and update SAS. + SharedAccessFilePolicy policy2 = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = SharedAccessFilePermissions.Read + }; + string sasToken2 = this.testShare.GetSharedAccessSignature(policy2); + creds.UpdateSASToken(sasToken2); + + // Extra check to make sure that we have actually updated the SAS token. + CloudFileShare share = new CloudFileShare(this.testShare.Uri, creds); + CloudFile testFile2 = share.GetRootDirectoryReference().GetFileReference("file2"); + + TestHelper.ExpectedException( + () => UploadText(testFile2, "file", Encoding.UTF8), + "Writing to a file while SAS does not allow for writing", + HttpStatusCode.NotFound); + + CloudFile testFile3 = this.testShare.GetRootDirectoryReference().GetFileReference("file3"); + testFile3.Create(0); + TestAccess(sasToken2, SharedAccessFilePermissions.Read, null, this.testShare, testFile); + } + + [TestMethod] + [Description("Test all combinations of file permissions against a share")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileShareSASCombinations() + { + for (int i = 1; i < 16; i++) + { + SharedAccessFilePermissions permissions = (SharedAccessFilePermissions)i; + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = permissions, + }; + string sasToken = this.testShare.GetSharedAccessSignature(policy); + + CloudFile testFile = this.testShare.GetRootDirectoryReference().GetFileReference("file" + i); + UploadText(testFile, "file", Encoding.UTF8); + FileSASTests.TestAccess(sasToken, permissions, null, this.testShare, testFile); + } + } + + [TestMethod] + [Description("Test all combinations of file permissions against a file")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileSASCombinations() + { + for (int i = 1; i < 8; i++) + { + CloudFile testFile = this.testShare.GetRootDirectoryReference().GetFileReference("file" + i); + SharedAccessFilePermissions permissions = (SharedAccessFilePermissions)i; + TestFileSAS(testFile, permissions, null); + } + } + + [TestMethod] + [Description("Test all combinations of file permissions against a file")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileSASHeaders() + { + for (int i = 1; i < 8; i++) + { + CloudFile testFile = this.testShare.GetRootDirectoryReference().GetFileReference("file" + i); + SharedAccessFilePermissions permissions = (SharedAccessFilePermissions)i; + SharedAccessFileHeaders headers = new SharedAccessFileHeaders() + { + CacheControl = "no-transform", + ContentDisposition = "attachment", + ContentEncoding = "gzip", + ContentLanguage = "tr,en", + ContentType = "text/html" + }; + + TestFileSAS(testFile, permissions, headers); + } + } + + [TestMethod] + [Description("Test SAS against a file directory")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileDirectorySAS() + { + CloudFileDirectory dir = this.testShare.GetRootDirectoryReference().GetDirectoryReference("dirfile"); + CloudFile file = dir.GetFileReference("dirfile"); + + dir.Create(); + file.Create(512); + + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = SharedAccessFilePermissions.Read | SharedAccessFilePermissions.List + }; + + string sasToken = file.GetSharedAccessSignature(policy); + CloudFileDirectory sasDir = new CloudFileDirectory(new Uri(dir.Uri.AbsoluteUri + sasToken)); + TestHelper.ExpectedException( + () => sasDir.FetchAttributes(), + "Fetching attributes of a directory using a file SAS should fail", + HttpStatusCode.Forbidden); + + sasToken = this.testShare.GetSharedAccessSignature(policy); + sasDir = new CloudFileDirectory(new Uri(dir.Uri.AbsoluteUri + sasToken)); + sasDir.FetchAttributes(); + } + } +} diff --git a/Test/ClassLibraryCommon/File/FileTestBase.cs b/Test/ClassLibraryCommon/File/FileTestBase.cs index d70642b68..f22c6ee7a 100644 --- a/Test/ClassLibraryCommon/File/FileTestBase.cs +++ b/Test/ClassLibraryCommon/File/FileTestBase.cs @@ -26,19 +26,43 @@ namespace Microsoft.WindowsAzure.Storage.File { public partial class FileTestBase : TestBase { - public static List CreateFiles(CloudFileShare share, int count) - { - string name; - List files = new List(); - for (int i = 0; i < count; i++) - { - name = "ff" + Guid.NewGuid().ToString(); - CloudFile file = share.GetRootDirectoryReference().GetFileReference(name); - file.Create(0); - files.Add(name); - } - return files; - } + public static void WaitForCopy(CloudFile file) + { + bool copyInProgress = true; + while (copyInProgress) + { + Thread.Sleep(1000); + file.FetchAttributes(); + copyInProgress = (file.CopyState.Status == CopyStatus.Pending); + } + } + +#if TASK + public static void WaitForCopyTask(CloudFile file) + { + bool copyInProgress = true; + while (copyInProgress) + { + Thread.Sleep(1000); + file.FetchAttributesAsync().Wait(); + copyInProgress = (file.CopyState.Status == CopyStatus.Pending); + } + } +#endif + + public static List CreateFiles(CloudFileShare share, int count) + { + string name; + List files = new List(); + for (int i = 0; i < count; i++) + { + name = "ff" + Guid.NewGuid().ToString(); + CloudFile file = share.GetRootDirectoryReference().GetFileReference(name); + file.Create(0); + files.Add(name); + } + return files; + } #if TASK public static List CreateFilesTask(CloudFileShare share, int count) diff --git a/Test/ClassLibraryCommon/Queue/CloudQueueMessageEncryptionTests.cs b/Test/ClassLibraryCommon/Queue/CloudQueueMessageEncryptionTests.cs index 0f432db8c..ea6da008f 100644 --- a/Test/ClassLibraryCommon/Queue/CloudQueueMessageEncryptionTests.cs +++ b/Test/ClassLibraryCommon/Queue/CloudQueueMessageEncryptionTests.cs @@ -364,5 +364,73 @@ public void CloudQueueMessageValidateEncryption() queue.DeleteIfExists(); } } + + [TestMethod] + [Description("Test adding/retrieving message using RequireEncryption flag.")] + [TestCategory(ComponentCategory.Queue)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudQueueMessageEncryptionWithStrictMode() + { + // Create the Key to be used for wrapping. + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + + // Create the resolver to be used for unwrapping. + DictionaryKeyResolver resolver = new DictionaryKeyResolver(); + resolver.Add(aesKey); + + CloudQueueClient client = GenerateCloudQueueClient(); + string name = GenerateNewQueueName(); + CloudQueue queue = client.GetQueueReference(name); + try + { + queue.CreateIfNotExists(); + + string messageStr = Guid.NewGuid().ToString(); + CloudQueueMessage message = new CloudQueueMessage(messageStr); + + QueueEncryptionPolicy policy = new QueueEncryptionPolicy(aesKey, null); + + // Add message with policy. + QueueRequestOptions createOptions = new QueueRequestOptions() { EncryptionPolicy = policy }; + createOptions.RequireEncryption = true; + + queue.AddMessage(message, null, null, createOptions, null); + + // Set policy to null and add message while RequireEncryption flag is still set to true. This should throw. + createOptions.EncryptionPolicy = null; + + TestHelper.ExpectedException( + () => queue.AddMessage(message, null, null, createOptions, null), + "Not specifying a policy when RequireEnryption is set to true should throw."); + + // Retrieve message + QueueEncryptionPolicy retrPolicy = new QueueEncryptionPolicy(null, resolver); + QueueRequestOptions retrieveOptions = new QueueRequestOptions() { EncryptionPolicy = retrPolicy }; + retrieveOptions.RequireEncryption = true; + + CloudQueueMessage retrMessage = queue.GetMessage(null, retrieveOptions, null); + + // Update message with plain text. + string updatedMessage = Guid.NewGuid().ToString("N"); + retrMessage.SetMessageContent(updatedMessage); + + queue.UpdateMessage(retrMessage, TimeSpan.FromSeconds(0), MessageUpdateFields.Content | MessageUpdateFields.Visibility); + + // Retrieve updated message with RequireEncryption flag but no metadata on the service. This should throw. + TestHelper.ExpectedException( + () => queue.GetMessage(null, retrieveOptions, null), + "Retrieving with RequireEncryption set to true and no metadata on the service should fail."); + + // Set RequireEncryption to false and retrieve. + retrieveOptions.RequireEncryption = false; + queue.GetMessage(null, retrieveOptions, null); + } + finally + { + queue.DeleteIfExists(); + } + } } } diff --git a/Test/ClassLibraryCommon/Queue/CloudQueueTest.cs b/Test/ClassLibraryCommon/Queue/CloudQueueTest.cs index 16f34f4c9..b8dfa3bf1 100644 --- a/Test/ClassLibraryCommon/Queue/CloudQueueTest.cs +++ b/Test/ClassLibraryCommon/Queue/CloudQueueTest.cs @@ -230,83 +230,7 @@ public void CloudQueueCreateFromUri() queue.Delete(); } - - [TestMethod] - [Description("Create a queue")] - [TestCategory(ComponentCategory.Queue)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void CloudQueueCreateUsingDifferentVersionHeader() - { - string name = GenerateNewQueueName(); - - CloudQueueClient client = GenerateCloudQueueClient(); - CloudQueue queue = client.GetQueueReference(name); - - OperationContext opContext = new OperationContext(); - opContext.SendingRequest += (obj, args) => args.Request.Headers[Constants.HeaderConstants.StorageVersionHeader] = "2011-08-18"; - - queue.Create(null, opContext); - Assert.AreEqual((int)HttpStatusCode.Created, opContext.LastResult.HttpStatusCode); - - queue.DeleteIfExists(); - } - - [TestMethod] - [Description("Create a queue with APM")] - [TestCategory(ComponentCategory.Queue)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void CloudQueueCreateUsingDifferentVersionHeaderAPM() - { - string name = GenerateNewQueueName(); - - CloudQueueClient client = GenerateCloudQueueClient(); - CloudQueue queue = client.GetQueueReference(name); - - OperationContext opContext = new OperationContext(); - opContext.SendingRequest += (obj, args) => args.Request.Headers[Constants.HeaderConstants.StorageVersionHeader] = "2011-08-18"; - - using (AutoResetEvent waitHandle = new AutoResetEvent(false)) - { - IAsyncResult result = queue.BeginCreate(null, opContext, ar => waitHandle.Set(), null); - waitHandle.WaitOne(); - queue.EndCreate(result); - - Assert.AreEqual((int)HttpStatusCode.Created, opContext.LastResult.HttpStatusCode); - - result = queue.BeginDeleteIfExists(ar => waitHandle.Set(), null); - waitHandle.WaitOne(); - queue.EndDeleteIfExists(result); - } - } - -#if TASK - [TestMethod] - [Description("Create a queue")] - [TestCategory(ComponentCategory.Queue)] - [TestCategory(TestTypeCategory.UnitTest)] - [TestCategory(SmokeTestCategory.NonSmoke)] - [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] - public void CloudQueueCreateUsingDifferentVersionHeaderTask() - { - string name = GenerateNewQueueName(); - - CloudQueueClient client = GenerateCloudQueueClient(); - CloudQueue queue = client.GetQueueReference(name); - - OperationContext opContext = new OperationContext(); - opContext.SendingRequest += (obj, args) => args.Request.Headers[Constants.HeaderConstants.StorageVersionHeader] = "2011-08-18"; - - queue.CreateAsync(null, opContext).Wait(); - Assert.AreEqual((int)HttpStatusCode.Created, opContext.LastResult.HttpStatusCode); - - queue.DeleteIfExistsAsync().Wait(); - } -#endif - + [TestMethod] [Description("Try to create a queue after it is created")] [TestCategory(ComponentCategory.Queue)] @@ -1510,6 +1434,7 @@ public void QueueEmptyHeaderSigningTest() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] public void CloudQueueOldSASVersion() { CloudQueueClient client = GenerateCloudQueueClient(); diff --git a/Test/ClassLibraryCommon/Table/SAS/TableSasUnitTests.cs b/Test/ClassLibraryCommon/Table/SAS/TableSasUnitTests.cs index 79ca9097d..0ff055f78 100644 --- a/Test/ClassLibraryCommon/Table/SAS/TableSasUnitTests.cs +++ b/Test/ClassLibraryCommon/Table/SAS/TableSasUnitTests.cs @@ -167,6 +167,7 @@ public void TableSASConstructors() [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + [Obsolete("The overload for GetSharedAccessSignature that takes a SAS version has been deprecated because the SAS tokens generated using the current version work fine with old libraries.")] public void TableSASConstructorsOldVersion() { CloudTableClient tableClient = GenerateCloudTableClient(); diff --git a/Test/ClassLibraryCommon/Table/TableEntityEncryptionTests.cs b/Test/ClassLibraryCommon/Table/TableEntityEncryptionTests.cs index 48907b580..57a49a8d9 100644 --- a/Test/ClassLibraryCommon/Table/TableEntityEncryptionTests.cs +++ b/Test/ClassLibraryCommon/Table/TableEntityEncryptionTests.cs @@ -310,11 +310,24 @@ private void DoInsertPOCOEntityEncryptionWithAttributesAndResolver(TablePayloadF Assert.AreEqual(ent.PartitionKey, retrievedEntity.PartitionKey); Assert.AreEqual(ent.RowKey, retrievedEntity.RowKey); - // Since we store encrypted properties as byte arrays, if a POCO entity is being read as-is, it will not be assign the binary - // values to strings. - Assert.IsNull(retrievedEntity.foo); - Assert.IsNull(retrievedEntity.A); - Assert.IsNull(retrievedEntity.B); + // Since we store encrypted properties as byte arrays, if a POCO entity is being read as-is, odata will not assign the binary + // values to strings. In JSON no metadata, the service does not return the types and the client lib does the parsing and reads the + // base64 encoded string as-is. + if (format == TablePayloadFormat.JsonNoMetadata) + { + Assert.IsNotNull(retrievedEntity.foo); + Assert.IsNotNull(retrievedEntity.A); + Assert.IsNotNull(retrievedEntity.B); + Assert.AreEqual(ent.foo.GetType(), retrievedEntity.foo.GetType()); + Assert.AreEqual(ent.A.GetType(), retrievedEntity.A.GetType()); + Assert.AreEqual(ent.B.GetType(), retrievedEntity.B.GetType()); + } + else + { + Assert.IsNull(retrievedEntity.foo); + Assert.IsNull(retrievedEntity.A); + Assert.IsNull(retrievedEntity.B); + } // Retrieve entity without decryption and confirm that all 3 properties were encrypted. // No need for an encryption resolver while retrieving the entity. @@ -326,9 +339,19 @@ private void DoInsertPOCOEntityEncryptionWithAttributesAndResolver(TablePayloadF Assert.IsNotNull(retrievedEntity); Assert.AreEqual(ent.PartitionKey, retrievedDynamicEntity.PartitionKey); Assert.AreEqual(ent.RowKey, retrievedDynamicEntity.RowKey); - Assert.AreNotEqual(ent.foo.GetType(), retrievedDynamicEntity.Properties["foo"].GetType()); - Assert.AreNotEqual(ent.A.GetType(), retrievedDynamicEntity.Properties["A"].GetType()); - Assert.AreNotEqual(ent.B.GetType(), retrievedDynamicEntity.Properties["B"].GetType()); + + if (format == TablePayloadFormat.JsonNoMetadata) + { + Assert.AreEqual(EdmType.String, retrievedDynamicEntity.Properties["foo"].PropertyType); + Assert.AreEqual(EdmType.String, retrievedDynamicEntity.Properties["A"].PropertyType); + Assert.AreEqual(EdmType.String, retrievedDynamicEntity.Properties["B"].PropertyType); + } + else + { + Assert.AreEqual(EdmType.Binary, retrievedDynamicEntity.Properties["foo"].PropertyType); + Assert.AreEqual(EdmType.Binary, retrievedDynamicEntity.Properties["A"].PropertyType); + Assert.AreEqual(EdmType.Binary, retrievedDynamicEntity.Properties["B"].PropertyType); + } // Retrieve entity and decrypt. TableRequestOptions retrieveOptions = new TableRequestOptions() { EncryptionPolicy = new TableEncryptionPolicy(null, resolver) }; @@ -526,6 +549,68 @@ private void DoTableOperationReplaceEncryption(TablePayloadFormat format) Assert.AreEqual(replaceEntity.Properties["B"], retrievedEntity.Properties["B"]); } + [TestMethod] + [Description("Swap rows and ensure decryption fails.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TableEncryptionValidateSwappingPropertiesThrows() + { + // Create the Key to be used for wrapping. + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + + TableRequestOptions options = new TableRequestOptions() + { + EncryptionPolicy = new TableEncryptionPolicy(aesKey, null), + + EncryptionResolver = (pk, rk, propName) => + { + if (propName == "Prop1") + { + return true; + } + + return false; + } + }; + + // Insert Entities + DynamicTableEntity baseEntity1 = new DynamicTableEntity("test1", "foo1"); + baseEntity1.Properties.Add("Prop1", new EntityProperty("Value1")); + currentTable.Execute(TableOperation.Insert(baseEntity1), options); + + DynamicTableEntity baseEntity2 = new DynamicTableEntity("test1", "foo2"); + baseEntity2.Properties.Add("Prop1", new EntityProperty("Value2")); + currentTable.Execute(TableOperation.Insert(baseEntity2), options); + + // Retrieve entity1 (Do not set encryption policy) + TableResult result = currentTable.Execute(TableOperation.Retrieve(baseEntity1.PartitionKey, baseEntity1.RowKey)); + DynamicTableEntity retrievedEntity = result.Result as DynamicTableEntity; + + // Replace entity2 with encrypted entity1's properties (Do not set encryption policy). + DynamicTableEntity replaceEntity = new DynamicTableEntity(baseEntity2.PartitionKey, baseEntity2.RowKey) { ETag = baseEntity2.ETag }; + replaceEntity.Properties = retrievedEntity.Properties; + currentTable.Execute(TableOperation.Replace(replaceEntity)); + + // Try to retrieve entity2 + // Create the resolver to be used for unwrapping. + DictionaryKeyResolver resolver = new DictionaryKeyResolver(); + resolver.Add(aesKey); + + TableRequestOptions retrieveOptions = new TableRequestOptions() { EncryptionPolicy = new TableEncryptionPolicy(null, resolver) }; + + try + { + result = currentTable.Execute(TableOperation.Retrieve(baseEntity2.PartitionKey, baseEntity2.RowKey), retrieveOptions); + Assert.Fail(); + } + catch (StorageException ex) + { + Assert.IsInstanceOfType(ex.InnerException, typeof(CryptographicException)); + } + } + #region batch [TestMethod] [Description("TableOperation Insert Or Replace")] @@ -752,10 +837,19 @@ private void DoTableOperationValidateEncryption(TablePayloadFormat format) Assert.AreEqual(ent.RowKey, retrievedEntity.RowKey); // Properties having the same value should be encrypted to different values. - CollectionAssert.AreNotEqual(retrievedEntity.Properties["encprop"].BinaryValue, retrievedEntity.Properties["encprop2"].BinaryValue); - Assert.AreNotEqual(ent.Properties["encprop"].PropertyType, retrievedEntity.Properties["encprop"].PropertyType); - Assert.AreNotEqual(ent.Properties["encprop2"].PropertyType, retrievedEntity.Properties["encprop2"].PropertyType); - Assert.AreNotEqual(ent.Properties["encprop3"].PropertyType, retrievedEntity.Properties["encprop3"].PropertyType); + if (format == TablePayloadFormat.JsonNoMetadata) + { + // With DTE and Json no metadata, if an encryption policy is not set, the client lib just reads the byte arrays as strings. + Assert.AreNotEqual(retrievedEntity.Properties["encprop"].StringValue, retrievedEntity.Properties["encprop2"].StringValue); + } + else + { + CollectionAssert.AreNotEqual(retrievedEntity.Properties["encprop"].BinaryValue, retrievedEntity.Properties["encprop2"].BinaryValue); + Assert.AreNotEqual(ent.Properties["encprop"].PropertyType, retrievedEntity.Properties["encprop"].PropertyType); + Assert.AreNotEqual(ent.Properties["encprop2"].PropertyType, retrievedEntity.Properties["encprop2"].PropertyType); + Assert.AreNotEqual(ent.Properties["encprop3"].PropertyType, retrievedEntity.Properties["encprop3"].PropertyType); + } + Assert.AreEqual(ent.Properties["notencprop"].Int32Value, retrievedEntity.Properties["notencprop"].Int32Value); } @@ -805,6 +899,198 @@ public void TableEncryptingUnsupportedPropertiesShouldThrow() } #endregion + [TestMethod] + [Description("TableOperation Insert/Get with RequireEncryption flag.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TableOperationEncryptionWithStrictMode() + { + // Insert Entity + DynamicTableEntity ent = new DynamicTableEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; + ent.Properties.Add("foo2", new EntityProperty(string.Empty)); + ent.Properties.Add("foo", new EntityProperty("bar")); + ent.Properties.Add("fooint", new EntityProperty(1234)); + + // Create the Key to be used for wrapping. + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + + // Create the resolver to be used for unwrapping. + DictionaryKeyResolver resolver = new DictionaryKeyResolver(); + resolver.Add(aesKey); + + TableRequestOptions options = new TableRequestOptions() + { + EncryptionPolicy = new TableEncryptionPolicy(aesKey, null), + + EncryptionResolver = (pk, rk, propName) => + { + if (propName == "foo" || propName == "foo2") + { + return true; + } + + return false; + }, + + RequireEncryption = true + }; + + currentTable.Execute(TableOperation.Insert(ent), options, null); + + // Insert an entity when RequireEncryption is set to true but no policy is specified. This should throw. + options.EncryptionPolicy = null; + + TestHelper.ExpectedException( + () => currentTable.Execute(TableOperation.Insert(ent), options, null), + "Not specifying a policy when RequireEncryption is set to true should throw."); + + // Retrieve Entity + TableRequestOptions retrieveOptions = new TableRequestOptions() + { + PropertyResolver = (pk, rk, propName, propValue) => + { + if (propName == "fooint") + { + return EdmType.Int32; + } + + return (EdmType)0; + }, + + EncryptionPolicy = new TableEncryptionPolicy(null, resolver), + + RequireEncryption = true + }; + + TableOperation operation = TableOperation.Retrieve(ent.PartitionKey, ent.RowKey); + Assert.IsFalse(operation.IsTableEntity); + TableResult result = currentTable.Execute(operation, retrieveOptions, null); + + // Replace entity with plain text. + ent.ETag = (result.Result as DynamicTableEntity).ETag; + currentTable.Execute(TableOperation.Replace(ent)); + + // Retrieve with RequireEncryption flag but no metadata on the service. This should throw. + TestHelper.ExpectedException( + () => currentTable.Execute(operation, retrieveOptions, null), + "Retrieving with RequireEncryption set to true and no metadata on the service should fail."); + + // Set RequireEncryption flag to true and retrieve. + retrieveOptions.RequireEncryption = false; + result = currentTable.Execute(operation, retrieveOptions, null); + } + + [TestMethod] + [Description("TableOperation InsertOrMerge/Merge with RequireEncryption flag.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TableOperationEncryptionWithStrictModeOnMerge() + { + // Insert Entity + DynamicTableEntity ent = new DynamicTableEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; + ent.Properties.Add("foo2", new EntityProperty(string.Empty)); + ent.Properties.Add("foo", new EntityProperty("bar")); + ent.Properties.Add("fooint", new EntityProperty(1234)); + ent.ETag = "*"; + + TableRequestOptions options = new TableRequestOptions() + { + RequireEncryption = true + }; + + try + { + currentTable.Execute(TableOperation.Merge(ent), options, null); + Assert.Fail("Merge with RequireEncryption on should fail."); + } + catch (StorageException ex) + { + Assert.AreEqual(ex.Message, SR.EncryptionPolicyMissingInStrictMode); + } + + try + { + currentTable.Execute(TableOperation.InsertOrMerge(ent), options, null); + Assert.Fail("InsertOrMerge with RequireEncryption on should fail."); + } + catch (StorageException ex) + { + Assert.AreEqual(ex.Message, SR.EncryptionPolicyMissingInStrictMode); + } + + // Create the Key to be used for wrapping. + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + options.EncryptionPolicy = new TableEncryptionPolicy(aesKey, null); + + try + { + currentTable.Execute(TableOperation.Merge(ent), options, null); + Assert.Fail("Merge with an EncryptionPolicy should fail."); + } + catch (StorageException ex) + { + Assert.AreEqual(ex.Message, SR.EncryptionNotSupportedForOperation); + } + + try + { + currentTable.Execute(TableOperation.InsertOrMerge(ent), options, null); + Assert.Fail("InsertOrMerge with an EncryptionPolicy should fail."); + } + catch (StorageException ex) + { + Assert.AreEqual(ex.Message, SR.EncryptionNotSupportedForOperation); + } + } + + [TestMethod] + [Description("Basic query test with mixed mode.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TableQueryEncryptionMixedMode() + { + // Insert Entity + EncryptedBaseEntity ent1 = new EncryptedBaseEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; + ent1.Populate(); + + EncryptedBaseEntity ent2 = new EncryptedBaseEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; + ent2.Populate(); + + // Create the Key to be used for wrapping. + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + + TableRequestOptions options = new TableRequestOptions() { EncryptionPolicy = new TableEncryptionPolicy(aesKey, null) }; + + // Insert an encrypted entity. + currentTable.Execute(TableOperation.Insert(ent1), options, null); + + // Insert a non-encrypted entity. + currentTable.Execute(TableOperation.Insert(ent2), null, null); + + // Create the resolver to be used for unwrapping. + DictionaryKeyResolver resolver = new DictionaryKeyResolver(); + resolver.Add(aesKey); + + options = new TableRequestOptions() { EncryptionPolicy = new TableEncryptionPolicy(null, resolver) }; + + // Set RequireEncryption to false and query. This will succeed. + options.RequireEncryption = false; + TableQuery query = new TableQuery(); + currentTable.ExecuteQuery(query, options).ToList(); + + // Set RequireEncryption to true and query. This will fail because it can't find the metadata for the second enctity on the server. + options.RequireEncryption = true; + TestHelper.ExpectedException( + () => currentTable.ExecuteQuery(query, options).ToList(), + "All entities retrieved should be encrypted when RequireEncryption is set to true."); + } + private static DynamicTableEntity GenerateRandomEntity(string pk) { DynamicTableEntity ent = new DynamicTableEntity(); diff --git a/Test/Common/TestHelper.Common.cs b/Test/Common/TestHelper.Common.cs index c7549dbbc..2a6f29d3b 100644 --- a/Test/Common/TestHelper.Common.cs +++ b/Test/Common/TestHelper.Common.cs @@ -396,6 +396,42 @@ internal static void VerifyServiceStats(ServiceStats stats) } } + internal static void AssertFileServicePropertiesAreEqual(FileServiceProperties propsA, FileServiceProperties propsB) + { + if (propsA.Cors != null && propsB.Cors != null) + { + Assert.AreEqual(propsA.Cors.CorsRules.Count, propsB.Cors.CorsRules.Count); + + // Check that rules are equal and in the same order. + for (int i = 0; i < propsA.Cors.CorsRules.Count; i++) + { + CorsRule ruleA = propsA.Cors.CorsRules.ElementAt(i); + CorsRule ruleB = propsB.Cors.CorsRules.ElementAt(i); + + Assert.IsTrue( + ruleA.AllowedOrigins.Count == ruleB.AllowedOrigins.Count + && !ruleA.AllowedOrigins.Except(ruleB.AllowedOrigins).Any()); + + Assert.IsTrue( + ruleA.ExposedHeaders.Count == ruleB.ExposedHeaders.Count + && !ruleA.ExposedHeaders.Except(ruleB.ExposedHeaders).Any()); + + Assert.IsTrue( + ruleA.AllowedHeaders.Count == ruleB.AllowedHeaders.Count + && !ruleA.AllowedHeaders.Except(ruleB.AllowedHeaders).Any()); + + Assert.IsTrue(ruleA.AllowedMethods == ruleB.AllowedMethods); + + Assert.IsTrue(ruleA.MaxAgeInSeconds == ruleB.MaxAgeInSeconds); + } + } + else + { + Assert.IsNull(propsA.Cors); + Assert.IsNull(propsB.Cors); + } + } + internal static void AssertServicePropertiesAreEqual(ServiceProperties propsA, ServiceProperties propsB) { if (propsA.Logging != null && propsB.Logging != null) diff --git a/Test/WindowsDesktop/DictionaryKeyResolver.cs b/Test/WindowsDesktop/DictionaryKeyResolver.cs index 0b5c8d4cd..74657533a 100644 --- a/Test/WindowsDesktop/DictionaryKeyResolver.cs +++ b/Test/WindowsDesktop/DictionaryKeyResolver.cs @@ -31,14 +31,11 @@ public void Add(IKey key) keys[key.Kid] = key; } -#pragma warning disable 1998 public async Task ResolveKeyAsync(string kid, CancellationToken token) { IKey result; keys.TryGetValue(kid, out result); return result; } -#pragma warning restore 1998 - } } diff --git a/Test/WindowsDesktop/Properties/AssemblyInfo.cs b/Test/WindowsDesktop/Properties/AssemblyInfo.cs index 783934bf5..933371a6c 100644 --- a/Test/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Test/WindowsDesktop/Properties/AssemblyInfo.cs @@ -32,6 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.4.1.0")] -[assembly: AssemblyFileVersion("4.4.1.0")] -[assembly: AssemblyInformationalVersion("4.4.1.0-preview")] +[assembly: AssemblyVersion("5.0.0.0")] +[assembly: AssemblyFileVersion("5.0.0.0")] diff --git a/Test/WindowsDesktop/packages.config b/Test/WindowsDesktop/packages.config index 95329bcc5..1b9629636 100644 --- a/Test/WindowsDesktop/packages.config +++ b/Test/WindowsDesktop/packages.config @@ -1,18 +1,18 @@  + + + + - - - + + + - - - - \ No newline at end of file diff --git a/Test/WindowsPhone/Properties/AssemblyInfo.cs b/Test/WindowsPhone/Properties/AssemblyInfo.cs index b61a10ed1..47229ebd3 100644 --- a/Test/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone/Properties/AssemblyInfo.cs @@ -32,7 +32,6 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("4.4.1.0")] -[assembly: AssemblyFileVersion("4.4.1.0")] -[assembly: AssemblyInformationalVersion("4.4.1.0-preview")] +[assembly: AssemblyVersion("5.0.0.0")] +[assembly: AssemblyFileVersion("5.0.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhone81/Properties/AssemblyInfo.cs b/Test/WindowsPhone81/Properties/AssemblyInfo.cs index 5e4e1b141..0e216c0b7 100644 --- a/Test/WindowsPhone81/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone81/Properties/AssemblyInfo.cs @@ -33,7 +33,6 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("4.4.1.0")] -[assembly: AssemblyFileVersion("4.4.1.0")] -[assembly: AssemblyInformationalVersion("4.4.1.0-preview")] +[assembly: AssemblyVersion("5.0.0.0")] +[assembly: AssemblyFileVersion("5.0.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhoneCommon/Blob/BlobReadStreamTest.cs b/Test/WindowsPhoneCommon/Blob/BlobReadStreamTest.cs index 54bac4563..5eb32921d 100644 --- a/Test/WindowsPhoneCommon/Blob/BlobReadStreamTest.cs +++ b/Test/WindowsPhoneCommon/Blob/BlobReadStreamTest.cs @@ -105,8 +105,43 @@ public async Task PageBlobReadStreamBasicTestAsync() using (MemoryStream wholeBlob = new MemoryStream(buffer)) { - Stream readStream = await blob.OpenReadAsync(); - using (Stream blobStream = readStream) + using (Stream blobStream = await blob.OpenReadAsync()) + { + TestHelper.AssertStreamsAreEqual(wholeBlob, blobStream); + } + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Download a blob using CloudBlobStream")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobReadStreamBasicTestAsync() + { + byte[] buffer = GetRandomBuffer(4 * 1024 * 1024); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + await blob.AppendBlockAsync(wholeBlob); + } + + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + using (Stream blobStream = await blob.OpenReadAsync()) { TestHelper.AssertStreamsAreEqual(wholeBlob, blobStream); } @@ -244,6 +279,71 @@ await TestHelper.ExpectedExceptionAsync( } } + [TestMethod] + [Description("Modify a blob while downloading it using CloudBlobStream")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobReadLockToETagTestAsync() + { + byte[] outBuffer = new byte[1 * 1024 * 1024]; + byte[] buffer = GetRandomBuffer(2 * outBuffer.Length); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + blob.StreamMinimumReadSizeInBytes = outBuffer.Length; + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + await blob.AppendBlockAsync(wholeBlob); + } + + OperationContext opContext = new OperationContext(); + using (Stream blobStream = await blob.OpenReadAsync(null, null, opContext)) + { + Stream blobStreamForRead = blobStream; + await blobStreamForRead.ReadAsync(outBuffer, 0, outBuffer.Length); + await blob.SetMetadataAsync(); + await TestHelper.ExpectedExceptionAsync( + async () => await blobStreamForRead.ReadAsync(outBuffer, 0, outBuffer.Length), + opContext, + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + + opContext = new OperationContext(); + using (Stream blobStream = await blob.OpenReadAsync(null, null, opContext)) + { + Stream blobStreamForRead = blobStream; + long length = blobStreamForRead.Length; + await blob.SetMetadataAsync(); + await TestHelper.ExpectedExceptionAsync( + async () => await blobStreamForRead.ReadAsync(outBuffer, 0, outBuffer.Length), + opContext, + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + + opContext = new OperationContext(); + AccessCondition accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(DateTimeOffset.Now.Subtract(TimeSpan.FromHours(1))); + await blob.SetMetadataAsync(); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.OpenReadAsync(accessCondition, null, opContext), + opContext, + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + private static async Task BlobReadStreamSeekAndCompareAsync(Stream blobStream, byte[] bufferToCompare, long offset, int readSize, int expectedReadCount) { byte[] testBuffer = new byte[readSize]; @@ -379,5 +479,41 @@ public async Task PageBlobReadStreamSeekTestAsync() container.DeleteIfExistsAsync().Wait(); } } + + [TestMethod] + [Description("Seek and read in a CloudBlobStream")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobReadStreamSeekTestAsync() + { + byte[] buffer = GetRandomBuffer(3 * 1024 * 1024); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + blob.StreamMinimumReadSizeInBytes = 2 * 1024 * 1024; + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + await blob.AppendBlockAsync(wholeBlob); + } + + OperationContext opContext = new OperationContext(); + using (Stream blobStream = await blob.OpenReadAsync(null, null, opContext)) + { + int attempts = await BlobReadStreamSeekTestAsync(blobStream, blob.StreamMinimumReadSizeInBytes, buffer); + TestHelper.AssertNAttempts(opContext, attempts); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } } } diff --git a/Test/WindowsPhoneCommon/Blob/BlobTestBase.cs b/Test/WindowsPhoneCommon/Blob/BlobTestBase.cs index 56cf58f25..d43872463 100644 --- a/Test/WindowsPhoneCommon/Blob/BlobTestBase.cs +++ b/Test/WindowsPhoneCommon/Blob/BlobTestBase.cs @@ -57,6 +57,13 @@ public static async Task> CreateBlobsAsync(CloudBlobContainer conta await pageBlob.CreateAsync(0); blobs.Add(name); break; + + case BlobType.AppendBlob: + name = "ab" + Guid.NewGuid().ToString(); + CloudAppendBlob appendBlob = container.GetAppendBlobReference(name); + await appendBlob.CreateOrReplaceAsync(); + blobs.Add(name); + break; } } return blobs; @@ -81,7 +88,13 @@ public static async Task UploadTextAsync(CloudBlob blob, string text, Encoding e stream.Seek(0, SeekOrigin.Begin); blob.ServiceClient.DefaultRequestOptions.ParallelOperationThreadCount = 2; - if (blob.BlobType == BlobType.PageBlob) + if (blob.BlobType == BlobType.AppendBlob) + { + CloudAppendBlob blob1 = blob as CloudAppendBlob; + await blob1.CreateOrReplaceAsync(); + await blob1.AppendBlockAsync(stream, null); + } + else if (blob.BlobType == BlobType.PageBlob) { CloudPageBlob pageBlob = blob as CloudPageBlob; await pageBlob.UploadFromStreamAsync(stream, accessCondition, options, operationContext); diff --git a/Test/WindowsPhoneCommon/Blob/BlobUploadDownloadTest.cs b/Test/WindowsPhoneCommon/Blob/BlobUploadDownloadTest.cs index ceca579aa..fe357194b 100644 --- a/Test/WindowsPhoneCommon/Blob/BlobUploadDownloadTest.cs +++ b/Test/WindowsPhoneCommon/Blob/BlobUploadDownloadTest.cs @@ -140,6 +140,20 @@ await TestHelper.ExpectedExceptionAsync( "Page blobs must be 512-byte aligned"); } + [TestMethod] + [Description("Upload from file to a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobUploadDownloadFileAsync() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + await this.DoUploadDownloadFileAsync(blob, 0); + await this.DoUploadDownloadFileAsync(blob, 4096); + await this.DoUploadDownloadFileAsync(blob, 4097); + } + private async Task DoUploadDownloadFileAsync(ICloudBlob blob, int fileSize) { string inputFileName = Path.GetTempFileName(); @@ -153,9 +167,9 @@ private async Task DoUploadDownloadFileAsync(ICloudBlob blob, int fileSize) await file.WriteAsync(buffer, 0, buffer.Length); } + OperationContext context = new OperationContext(); await blob.UploadFromFileAsync(inputFileName, FileMode.Open); - OperationContext context = new OperationContext(); await blob.UploadFromFileAsync(inputFileName, FileMode.Open, null, null, context); Assert.IsNotNull(context.LastResult.ServiceRequestID); @@ -236,6 +250,22 @@ await TestHelper.ExpectedExceptionAsync( "Page blobs must be 512-byte aligned"); } + [TestMethod] + [Description("Upload a blob using a byte array")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobUploadFromByteArrayAsync() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + await this.DoUploadFromByteArrayTestAsync(blob, 4 * 512, 0, 4 * 512); + await this.DoUploadFromByteArrayTestAsync(blob, 4 * 512, 0, 2 * 512); + await this.DoUploadFromByteArrayTestAsync(blob, 4 * 512, 1 * 512, 2 * 512); + await this.DoUploadFromByteArrayTestAsync(blob, 4 * 512, 2 * 512, 2 * 512); + await this.DoUploadFromByteArrayTestAsync(blob, 512, 0, 511); + } + private async Task DoUploadFromByteArrayTestAsync(ICloudBlob blob, int bufferSize, int bufferOffset, int count) { byte[] buffer = GetRandomBuffer(bufferSize); @@ -309,6 +339,34 @@ public async Task CloudPageBlobDownloadToByteArrayAsyncOverload() await this.DoDownloadToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, true); } + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobDownloadToByteArrayAsync() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + await this.DoDownloadToByteArrayAsyncTest(blob, 1 * 512, 2 * 512, 0, false); + await this.DoDownloadToByteArrayAsyncTest(blob, 1 * 512, 2 * 512, 1 * 512, false); + await this.DoDownloadToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, false); + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobDownloadToByteArrayAsyncOverload() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + await this.DoDownloadToByteArrayAsyncTest(blob, 1 * 512, 2 * 512, 0, true); + await this.DoDownloadToByteArrayAsyncTest(blob, 1 * 512, 2 * 512, 1 * 512, true); + await this.DoDownloadToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, true); + } + private async Task DoDownloadToByteArrayAsyncTest(ICloudBlob blob, int blobSize, int bufferSize, int bufferOffset, bool isOverload) { int downloadLength; @@ -318,37 +376,37 @@ private async Task DoDownloadToByteArrayAsyncTest(ICloudBlob blob, int blobSize, using (MemoryStream originalBlob = new MemoryStream(buffer)) { - if (!isOverload) - { - await blob.UploadFromStreamAsync(originalBlob); - downloadLength = await blob.DownloadToByteArrayAsync(resultBuffer, bufferOffset); - } - else - { - await blob.UploadFromStreamAsync(originalBlob); - OperationContext context = new OperationContext(); - downloadLength = await blob.DownloadToByteArrayAsync(resultBuffer, bufferOffset, null, null, context); - } + await blob.UploadFromStreamAsync(originalBlob); + } - int downloadSize = Math.Min(blobSize, bufferSize - bufferOffset); - Assert.AreEqual(downloadSize, downloadLength); + if (!isOverload) + { + downloadLength = await blob.DownloadToByteArrayAsync(resultBuffer, bufferOffset); + } + else + { + OperationContext context = new OperationContext(); + downloadLength = await blob.DownloadToByteArrayAsync(resultBuffer, bufferOffset, null, null, context); + } - for (int i = 0; i < blob.Properties.Length; i++) - { - Assert.AreEqual(buffer[i], resultBuffer[bufferOffset + i]); - } + int downloadSize = Math.Min(blobSize, bufferSize - bufferOffset); + Assert.AreEqual(downloadSize, downloadLength); - for (int j = 0; j < bufferOffset; j++) - { - Assert.AreEqual(0, resultBuffer2[j]); - } + for (int i = 0; i < blob.Properties.Length; i++) + { + Assert.AreEqual(buffer[i], resultBuffer[bufferOffset + i]); + } - if (bufferOffset + blobSize < bufferSize) + for (int j = 0; j < bufferOffset; j++) + { + Assert.AreEqual(0, resultBuffer2[j]); + } + + if (bufferOffset + blobSize < bufferSize) + { + for (int k = bufferOffset + blobSize; k < bufferSize; k++) { - for (int k = bufferOffset + blobSize; k < bufferSize; k++) - { - Assert.AreEqual(0, resultBuffer2[k]); - } + Assert.AreEqual(0, resultBuffer2[k]); } } } @@ -453,6 +511,56 @@ public async Task CloudPageBlobDownloadRangeToByteArrayAsyncOverload() await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 512, 0, 512, true); } + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobDownloadRangeToByteArrayAsync() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 0, 1 * 512, 1 * 512, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, null, null, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, 1 * 512, null, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, 0, 1 * 512, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 1 * 512, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 2 * 512, false); + + // Edge cases + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 1023, 1023, 1, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 0, 1023, 1, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 0, 0, 1, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 0, 512, 1, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 512, 1023, 1, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 512, 0, 512, false); + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobDownloadRangeToByteArrayAsyncOverload() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 0, 1 * 512, 1 * 512, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, null, null, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, 1 * 512, null, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, 0, 1 * 512, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 1 * 512, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 2 * 512, true); + + // Edge cases + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 1023, 1023, 1, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 0, 1023, 1, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 0, 0, 1, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 0, 512, 1, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 512, 1023, 1, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 512, 0, 512, true); + } + /// /// Single put blob and get blob. /// @@ -471,40 +579,41 @@ private async Task DoDownloadRangeToByteArrayAsyncTest(ICloudBlob blob, int blob using (MemoryStream originalBlob = new MemoryStream(buffer)) { - if (!isOverload) - { - await blob.UploadFromStreamAsync(originalBlob); - downloadLength = await blob.DownloadRangeToByteArrayAsync(resultBuffer, bufferOffset, blobOffset, length); - } - else - { - await blob.UploadFromStreamAsync(originalBlob); - OperationContext context = new OperationContext(); - downloadLength = await blob.DownloadRangeToByteArrayAsync(resultBuffer, bufferOffset, blobOffset, length, null, null, context); - } + await blob.UploadFromStreamAsync(originalBlob); + } - int downloadSize = Math.Min(blobSize - (int)(blobOffset.HasValue ? blobOffset.Value : 0), bufferSize - bufferOffset); - if (length.HasValue && (length.Value < downloadSize)) - { - downloadSize = (int)length.Value; - } + if (!isOverload) + { + downloadLength = await blob.DownloadRangeToByteArrayAsync(resultBuffer, bufferOffset, blobOffset, length); + } + else + { + OperationContext context = new OperationContext(); + downloadLength = await blob.DownloadRangeToByteArrayAsync(resultBuffer, bufferOffset, blobOffset, length, null, null, context); + } - Assert.AreEqual(downloadSize, downloadLength); + int downloadSize = Math.Min(blobSize - (int)(blobOffset.HasValue ? blobOffset.Value : 0), bufferSize - bufferOffset); - for (int i = 0; i < bufferOffset; i++) - { - Assert.AreEqual(0, resultBuffer[i]); - } + if (length.HasValue && (length.Value < downloadSize)) + { + downloadSize = (int)length.Value; + } - for (int j = 0; j < downloadLength; j++) - { - Assert.AreEqual(buffer[(blobOffset.HasValue ? blobOffset.Value : 0) + j], resultBuffer[bufferOffset + j]); - } + Assert.AreEqual(downloadSize, downloadLength); - for (int k = bufferOffset + downloadLength; k < bufferSize; k++) - { - Assert.AreEqual(0, resultBuffer[k]); - } + for (int i = 0; i < bufferOffset; i++) + { + Assert.AreEqual(0, resultBuffer[i]); + } + + for (int j = 0; j < downloadLength; j++) + { + Assert.AreEqual(buffer[(blobOffset.HasValue ? blobOffset.Value : 0) + j], resultBuffer[bufferOffset + j]); + } + + for (int k = bufferOffset + downloadLength; k < bufferSize; k++) + { + Assert.AreEqual(0, resultBuffer[k]); } } diff --git a/Test/WindowsPhoneCommon/Blob/BlobWriteStreamTest.cs b/Test/WindowsPhoneCommon/Blob/BlobWriteStreamTest.cs index 3b0371d1a..fb81e3ab2 100644 --- a/Test/WindowsPhoneCommon/Blob/BlobWriteStreamTest.cs +++ b/Test/WindowsPhoneCommon/Blob/BlobWriteStreamTest.cs @@ -62,16 +62,18 @@ public async Task BlobWriteStreamOpenAndCloseAsync() { await container.CreateAsync(); + // Block blob tests CloudBlockBlob blockBlob = container.GetBlockBlobReference("blob1"); using (Stream writeStream = await blockBlob.OpenWriteAsync()) { } - CloudBlockBlob blockBlob2 = container.GetBlockBlobReference("blob1"); + CloudBlockBlob blockBlob2 = container.GetBlockBlobReference(blockBlob.Name); await blockBlob2.FetchAttributesAsync(); Assert.AreEqual(0, blockBlob2.Properties.Length); Assert.AreEqual(BlobType.BlockBlob, blockBlob2.Properties.BlobType); + // Page blob tests CloudPageBlob pageBlob = container.GetPageBlobReference("blob2"); OperationContext opContext = new OperationContext(); await TestHelper.ExpectedExceptionAsync( @@ -86,10 +88,21 @@ await TestHelper.ExpectedExceptionAsync( { } - CloudPageBlob pageBlob2 = container.GetPageBlobReference("blob2"); + CloudPageBlob pageBlob2 = container.GetPageBlobReference(pageBlob.Name); await pageBlob2.FetchAttributesAsync(); Assert.AreEqual(1024, pageBlob2.Properties.Length); Assert.AreEqual(BlobType.PageBlob, pageBlob2.Properties.BlobType); + + // Append blob test + CloudAppendBlob appendBlob = container.GetAppendBlobReference("blob3"); + using (Stream writeStream = await appendBlob.OpenWriteAsync(true)) + { + } + + CloudAppendBlob appendBlob2 = container.GetAppendBlobReference(appendBlob.Name); + await appendBlob2.FetchAttributesAsync(); + Assert.AreEqual(0, appendBlob2.Properties.Length); + Assert.AreEqual(BlobType.AppendBlob, appendBlob2.Properties.BlobType); } finally { @@ -349,6 +362,109 @@ await TestHelper.ExpectedExceptionAsync( } } + [TestMethod] + [Description("Create a blob using blob stream by specifying an access condition")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobWriteStreamOpenWithAccessConditionAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + await container.CreateAsync(); + + try + { + OperationContext context = new OperationContext(); + + CloudAppendBlob existingBlob = container.GetAppendBlobReference("blob"); + await existingBlob.CreateOrReplaceAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob2"); + AccessCondition accessCondition = AccessCondition.GenerateIfMatchCondition(existingBlob.Properties.ETag); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.OpenWriteAsync(true, accessCondition, null, context), + context, + "OpenWriteAsync with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + blob = container.GetAppendBlobReference("blob3"); + accessCondition = AccessCondition.GenerateIfNoneMatchCondition(existingBlob.Properties.ETag); + var blobStream = await blob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + blob = container.GetAppendBlobReference("blob4"); + accessCondition = AccessCondition.GenerateIfNoneMatchCondition("*"); + blobStream = await blob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + blob = container.GetAppendBlobReference("blob5"); + accessCondition = AccessCondition.GenerateIfModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(1)); + blobStream = await blob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + blob = container.GetAppendBlobReference("blob6"); + accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(-1)); + blobStream = await blob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfMatchCondition(existingBlob.Properties.ETag); + blobStream = await existingBlob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfMatchCondition(blob.Properties.ETag); + await TestHelper.ExpectedExceptionAsync( + async () => await existingBlob.OpenWriteAsync(true, accessCondition, null, context), + context, + "OpenWriteAsync with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + accessCondition = AccessCondition.GenerateIfNoneMatchCondition(blob.Properties.ETag); + blobStream = await existingBlob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfNoneMatchCondition(existingBlob.Properties.ETag); + await TestHelper.ExpectedExceptionAsync( + async () => await existingBlob.OpenWriteAsync(true, accessCondition, null, context), + context, + "OpenWriteAsync with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + accessCondition = AccessCondition.GenerateIfNoneMatchCondition("*"); + await TestHelper.ExpectedExceptionAsync( + async () => await existingBlob.OpenWriteAsync(true, accessCondition, null, context), + context, + "BlobWriteStream.Dispose with a non-met condition should fail", + HttpStatusCode.Conflict); + + accessCondition = AccessCondition.GenerateIfModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(-1)); + blobStream = await existingBlob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(1)); + await TestHelper.ExpectedExceptionAsync( + async () => await existingBlob.OpenWriteAsync(true, accessCondition, null, context), + context, + "OpenWriteAsync with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(1)); + blobStream = await existingBlob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(-1)); + await TestHelper.ExpectedExceptionAsync( + async () => await existingBlob.OpenWriteAsync(true, accessCondition, null, context), + context, + "OpenWriteAsync with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + } + finally + { + container.DeleteAsync().Wait(); + } + } + [TestMethod] [Description("Upload a block blob using blob stream and verify contents")] [TestCategory(ComponentCategory.Blob)] @@ -676,5 +792,143 @@ public async Task PageBlobWriteStreamFlushTestAsync() container.DeleteIfExistsAsync().Wait(); } } + + [TestMethod] + [Description("Upload an append blob using blob stream and verify contents")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobWriteStreamBasicTestAsync() + { + byte[] buffer = GetRandomBuffer(3 * 1024 * 1024); + + CloudBlobClient blobClient = GenerateCloudBlobClient(); + string name = GetRandomContainerName(); + CloudBlobContainer container = blobClient.GetContainerReference(name); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + using (MemoryStream wholeBlob = new MemoryStream()) + { + using (Stream writeStream = await blob.OpenWriteAsync(true)) + { + Stream blobStream = writeStream; + + for (int i = 0; i < 3; i++) + { + await blobStream.WriteAsync(buffer, 0, buffer.Length); + await wholeBlob.WriteAsync(buffer, 0, buffer.Length); + Assert.AreEqual(wholeBlob.Position, blobStream.Position); + } + + await blobStream.FlushAsync(); + } + + using (MemoryStream downloadedBlob = new MemoryStream()) + { + await blob.DownloadToStreamAsync(downloadedBlob); + TestHelper.AssertStreamsAreEqual(wholeBlob, downloadedBlob); + } + } + } + finally + { + container.DeleteAsync().Wait(); + } + } + + [TestMethod] + [Description("Check seek functionality.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobWriteStreamSeekTestAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + using (Stream writeStream = await blob.OpenWriteAsync(true)) + { + Stream blobStream = writeStream; + TestHelper.ExpectedException( + () => blobStream.Seek(1, SeekOrigin.Begin), + "Append blob write stream should not be seekable"); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Test the effects of blob stream's flush functionality")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobWriteStreamFlushTestAsync() + { + byte[] buffer = GetRandomBuffer(512 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamWriteSizeInBytes = 1 * 1024 * 1024; + using (MemoryStream wholeBlob = new MemoryStream()) + { + OperationContext opContext = new OperationContext(); + using (CloudBlobStream blobStream = await blob.OpenWriteAsync(true, null, null, opContext)) + { + for (int i = 0; i < 3; i++) + { + await blobStream.WriteAsync(buffer, 0, buffer.Length); + await wholeBlob.WriteAsync(buffer, 0, buffer.Length); + } + + Assert.AreEqual(2, opContext.RequestResults.Count); + + await blobStream.FlushAsync(); + + Assert.AreEqual(3, opContext.RequestResults.Count); + + await blobStream.FlushAsync(); + + Assert.AreEqual(3, opContext.RequestResults.Count); + + await blobStream.WriteAsync(buffer, 0, buffer.Length); + await wholeBlob.WriteAsync(buffer, 0, buffer.Length); + + Assert.AreEqual(3, opContext.RequestResults.Count); + + await Task.Factory.FromAsync(blobStream.BeginCommit, blobStream.EndCommit, null); + + Assert.AreEqual(4, opContext.RequestResults.Count); + } + + Assert.AreEqual(4, opContext.RequestResults.Count); + + using (MemoryStream downloadedBlob = new MemoryStream()) + { + await blob.DownloadToStreamAsync(downloadedBlob); + TestHelper.AssertStreamsAreEqual(wholeBlob, downloadedBlob); + } + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } } } diff --git a/Test/WindowsPhoneCommon/Blob/CloudAppendBlobTest.cs b/Test/WindowsPhoneCommon/Blob/CloudAppendBlobTest.cs new file mode 100644 index 000000000..d2456e73d --- /dev/null +++ b/Test/WindowsPhoneCommon/Blob/CloudAppendBlobTest.cs @@ -0,0 +1,1192 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- +namespace Microsoft.WindowsAzure.Storage.Blob +{ + using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Runtime.InteropServices.WindowsRuntime; + using System.Text; + using System.Threading.Tasks; + + [TestClass] + public class CloudAppendBlobTest : BlobTestBase + { + // Use TestInitialize to run code before running each test + [TestInitialize()] + public void MyTestInitialize() + { + if (TestBase.BlobBufferManager != null) + { + TestBase.BlobBufferManager.OutstandingBufferCount = 0; + } + } + + // Use TestCleanup to run code after each test has run + [TestCleanup()] + public void MyTestCleanup() + { + if (TestBase.BlobBufferManager != null) + { + Assert.AreEqual(0, TestBase.BlobBufferManager.OutstandingBufferCount); + } + } + + [TestMethod] + [Description("Create a zero-length append blob and then delete it")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobCreateAndDeleteAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + Assert.IsTrue(await blob.ExistsAsync()); + await blob.DeleteAsync(); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Try to delete a non-existing append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobDeleteIfExistsAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + Assert.IsFalse(await blob.DeleteIfExistsAsync()); + await blob.CreateOrReplaceAsync(); + Assert.IsTrue(await blob.DeleteIfExistsAsync()); + Assert.IsFalse(await blob.DeleteIfExistsAsync()); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Check a blob's existence")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobExistsAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + await container.CreateAsync(); + + try + { + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + + Assert.IsFalse(await blob2.ExistsAsync()); + + await blob.CreateOrReplaceAsync(); + + Assert.IsTrue(await blob2.ExistsAsync()); + Assert.AreEqual(0, blob2.Properties.Length); + + await blob.DeleteAsync(); + + Assert.IsFalse(await blob2.ExistsAsync()); + } + finally + { + container.DeleteAsync().Wait(); + } + } + + [TestMethod] + [Description("Verify the attributes of a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobFetchAttributesAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + Assert.AreEqual(0, blob.Properties.Length); + Assert.IsNotNull(blob.Properties.ETag); + Assert.IsTrue(blob.Properties.LastModified > DateTimeOffset.UtcNow.AddMinutes(-5)); + Assert.IsNull(blob.Properties.CacheControl); + Assert.IsNull(blob.Properties.ContentEncoding); + Assert.IsNull(blob.Properties.ContentLanguage); + Assert.IsNull(blob.Properties.ContentType); + Assert.IsNull(blob.Properties.ContentMD5); + Assert.AreEqual(LeaseStatus.Unspecified, blob.Properties.LeaseStatus); + Assert.AreEqual(BlobType.AppendBlob, blob.Properties.BlobType); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + await blob2.FetchAttributesAsync(); + Assert.AreEqual(0, blob2.Properties.Length); + Assert.AreEqual(blob.Properties.ETag, blob2.Properties.ETag); + Assert.AreEqual(blob.Properties.LastModified, blob2.Properties.LastModified); + Assert.IsNull(blob2.Properties.ContentEncoding); + Assert.IsNull(blob2.Properties.ContentLanguage); + Assert.AreEqual("application/octet-stream", blob2.Properties.ContentType); + Assert.IsNull(blob2.Properties.ContentMD5); + Assert.AreEqual(LeaseStatus.Unlocked, blob2.Properties.LeaseStatus); + Assert.AreEqual(BlobType.AppendBlob, blob2.Properties.BlobType); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Verify setting the properties of a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobSetPropertiesAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + string eTag = blob.Properties.ETag; + DateTimeOffset lastModified = blob.Properties.LastModified.Value; + + await Task.Delay(1000); + + blob.Properties.CacheControl = "no-transform"; + blob.Properties.ContentEncoding = "gzip"; + blob.Properties.ContentLanguage = "tr,en"; + blob.Properties.ContentMD5 = "MDAwMDAwMDA="; + blob.Properties.ContentType = "text/html"; + await blob.SetPropertiesAsync(); + Assert.IsTrue(blob.Properties.LastModified > lastModified); + Assert.AreNotEqual(eTag, blob.Properties.ETag); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + await blob2.FetchAttributesAsync(); + Assert.AreEqual("no-transform", blob2.Properties.CacheControl); + Assert.AreEqual("gzip", blob2.Properties.ContentEncoding); + Assert.AreEqual("tr,en", blob2.Properties.ContentLanguage); + Assert.AreEqual("MDAwMDAwMDA=", blob2.Properties.ContentMD5); + Assert.AreEqual("text/html", blob2.Properties.ContentType); + + CloudAppendBlob blob3 = container.GetAppendBlobReference("blob1"); + using (MemoryStream stream = new MemoryStream()) + { + BlobRequestOptions options = new BlobRequestOptions() + { + DisableContentMD5Validation = true, + }; + await blob3.DownloadToStreamAsync(stream, null, options, null); + } + AssertAreEqual(blob2.Properties, blob3.Properties); + + BlobResultSegment results = await container.ListBlobsSegmentedAsync(null); + CloudAppendBlob blob4 = (CloudAppendBlob)results.Results.First(); + AssertAreEqual(blob2.Properties, blob4.Properties); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Try retrieving properties of a block blob using an append blob reference")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobFetchAttributesInvalidTypeAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + CloudBlockBlob blob2 = container.GetBlockBlobReference("blob1"); + OperationContext operationContext = new OperationContext(); + + Assert.ThrowsException( + () => blob2.FetchAttributesAsync(null, null, operationContext).Wait(), + "Fetching attributes of an append blob using a block blob reference should fail"); + Assert.IsInstanceOfType(operationContext.LastResult.Exception.InnerException, typeof(InvalidOperationException)); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Verify that creating an append blob can also set its metadata")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobCreateWithMetadataAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.Metadata["key1"] = "value1"; + await blob.CreateOrReplaceAsync(); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + await blob2.FetchAttributesAsync(); + Assert.AreEqual(1, blob2.Metadata.Count); + Assert.AreEqual("value1", blob2.Metadata["key1"]); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Verify that an append blob's metadata can be updated")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobSetMetadataAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + await blob2.FetchAttributesAsync(); + Assert.AreEqual(0, blob2.Metadata.Count); + + OperationContext operationContext = new OperationContext(); + blob.Metadata["key1"] = null; + + Assert.ThrowsException( + () => blob.SetMetadataAsync(null, null, operationContext).Wait(), + "Metadata keys should have a non-null value"); + Assert.IsInstanceOfType(operationContext.LastResult.Exception.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = ""; + Assert.ThrowsException( + () => blob.SetMetadataAsync(null, null, operationContext).Wait(), + "Metadata keys should have a non-empty value"); + Assert.IsInstanceOfType(operationContext.LastResult.Exception.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = " "; + Assert.ThrowsException( + () => blob.SetMetadataAsync(null, null, operationContext).Wait(), + "Metadata keys should have a non-whitespace only value"); + Assert.IsInstanceOfType(operationContext.LastResult.Exception.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = "value1"; + await blob.SetMetadataAsync(); + + await blob2.FetchAttributesAsync(); + Assert.AreEqual(1, blob2.Metadata.Count); + Assert.AreEqual("value1", blob2.Metadata["key1"]); + + BlobResultSegment results = await container.ListBlobsSegmentedAsync(null, true, BlobListingDetails.Metadata, null, null, null, null); + CloudAppendBlob blob3 = (CloudAppendBlob)results.Results.First(); + Assert.AreEqual(1, blob3.Metadata.Count); + Assert.AreEqual("value1", blob3.Metadata["key1"]); + + blob.Metadata.Clear(); + await blob.SetMetadataAsync(); + + await blob2.FetchAttributesAsync(); + Assert.AreEqual(0, blob2.Metadata.Count); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Validate UploadFromStream.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobValidateUploadFromStreamAsync() + { + byte[] buffer = GetRandomBuffer(6 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + + using (MemoryStream memStream = new MemoryStream(buffer)) + { + await blob.UploadFromStreamAsync(memStream); + await blob.FetchAttributesAsync(); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + await blob.UploadFromStreamAsync(memStream); + await blob.FetchAttributesAsync(); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + await blob.UploadFromStreamAsync(memStream, null /* accessCondition */, null /* options */, null /* operationContext */); + await blob.FetchAttributesAsync(); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Validate AppendFromStream.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobValidateAppendFromStreamAsync() + { + // Every time append a buffer that is bigger than a single block. + byte[] buffer = GetRandomBuffer(6 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + using (MemoryStream memStream = new MemoryStream(buffer)) + { + await blob.AppendFromStreamAsync(memStream); + await blob.FetchAttributesAsync(); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + await blob.AppendFromStreamAsync(memStream); + await blob.FetchAttributesAsync(); + Assert.AreEqual(12 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + await blob.AppendFromStreamAsync(memStream, null /* accessCondition */, null /* options */, null /* operationContext */); + await blob.FetchAttributesAsync(); + Assert.AreEqual(18 * 1024 * 1024, blob.Properties.Length); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Validate AppendFromStream.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobValidateAppendFromStreamWithLengthAsync() + { + // Every time append a buffer that is bigger than a single block. + byte[] buffer = GetRandomBuffer(6 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + using (MemoryStream memStream = new MemoryStream(buffer)) + { + await blob.AppendFromStreamAsync(memStream, 5 * 1024 * 1024); + await blob.FetchAttributesAsync(); + Assert.AreEqual(5 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + await blob.AppendFromStreamAsync(memStream, 5 * 1024 * 1024); + await blob.FetchAttributesAsync(); + Assert.AreEqual(10 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + await blob.AppendFromStreamAsync(memStream, 5 * 1024 * 1024, null /* accessCondition */, null /* options */, null /* operationContext */); + await blob.FetchAttributesAsync(); + Assert.AreEqual(15 * 1024 * 1024, blob.Properties.Length); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Verify the append offset returned by the service.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobVerifyAppendOffsetAsync() + { + byte[] buffer = GetRandomBuffer(2 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + long offset = await blob.AppendBlockAsync(new MemoryStream(buffer)); + Assert.AreEqual(0, offset); + + offset = await blob.AppendBlockAsync(new MemoryStream(buffer)); + Assert.AreEqual(2 * 1024 * 1024, offset); + + offset = await blob.AppendBlockAsync(new MemoryStream(buffer)); + Assert.AreEqual(4 * 1024 * 1024, offset); + + offset = await blob.AppendBlockAsync(new MemoryStream(buffer)); + Assert.AreEqual(6 * 1024 * 1024, offset); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobAppendBlockAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + await container.CreateAsync(); + try + { + await this.CloudAppendBlobUploadFromStreamAsync(container, 6 * 512, null, null, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 6 * 512, null, null, 1024); + } + finally + { + container.DeleteAsync().Wait(); + } + } + + private async Task CloudAppendBlobUploadFromStreamAsync(CloudBlobContainer container, int size, AccessCondition accessCondition, OperationContext operationContext, int startOffset) + { + byte[] buffer = GetRandomBuffer(size); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + using (MemoryStream originalBlobStream = new MemoryStream()) + { + originalBlobStream.Write(buffer, startOffset, buffer.Length - startOffset); + + using (MemoryStream sourceStream = new MemoryStream(buffer)) + { + sourceStream.Seek(startOffset, SeekOrigin.Begin); + await blob.AppendBlockAsync(sourceStream, null, accessCondition, null, operationContext); + } + + using (MemoryStream downloadedBlobStream = new MemoryStream()) + { + await blob.DownloadToStreamAsync(downloadedBlobStream); + TestHelper.AssertStreamsAreEqualAtIndex( + originalBlobStream, + downloadedBlobStream, + 0, + 0, + (int)originalBlobStream.Length); + } + } + } + + [TestMethod] + [Description("Upload/Download text")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobDownloadTextAsync() + { + await this.DoTextUploadDownloadAsync("test", true); + await this.DoTextUploadDownloadAsync("char中文test", true); + } + + private async Task DoTextUploadDownloadAsync(string text, bool checkDifferentEncoding) + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateIfNotExistsAsync(); + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + + await blob.UploadTextAsync(text); + Assert.AreEqual(text, blob.DownloadTextAsync().Result); + if (checkDifferentEncoding) + { + Assert.AreNotEqual(text, blob.DownloadTextAsync(Encoding.Unicode, null, null, null).Result); + } + + await blob.UploadTextAsync(text, Encoding.Unicode, null, null, null); + Assert.AreEqual(text, blob.DownloadTextAsync(Encoding.Unicode, null, null, null).Result); + if (checkDifferentEncoding) + { + Assert.AreNotEqual(text, blob.DownloadTextAsync().Result); + } + + OperationContext context = new OperationContext(); + await blob.UploadTextAsync(text, Encoding.Unicode, null, null, context); + + // 2 because of Create, Appendblock. No set properties as no MD5 on phone SL + Assert.AreEqual(2, context.RequestResults.Count); + await blob.DownloadTextAsync(Encoding.Unicode, null, null, context); + Assert.AreEqual(3, context.RequestResults.Count); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Create snapshots of an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobSnapshotAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + MemoryStream originalData = new MemoryStream(GetRandomBuffer(1024)); + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + await blob.AppendBlockAsync(originalData, null); + Assert.IsFalse(blob.IsSnapshot); + Assert.IsNull(blob.SnapshotTime, "Root blob has SnapshotTime set"); + Assert.IsFalse(blob.SnapshotQualifiedUri.Query.Contains("snapshot")); + Assert.AreEqual(blob.Uri, blob.SnapshotQualifiedUri); + + CloudAppendBlob snapshot1 = await blob.CreateSnapshotAsync(); + Assert.AreEqual(blob.Properties.ETag, snapshot1.Properties.ETag); + Assert.AreEqual(blob.Properties.LastModified, snapshot1.Properties.LastModified); + Assert.IsTrue(snapshot1.IsSnapshot); + Assert.IsNotNull(snapshot1.SnapshotTime, "Snapshot does not have SnapshotTime set"); + Assert.AreEqual(blob.Uri, snapshot1.Uri); + Assert.AreNotEqual(blob.SnapshotQualifiedUri, snapshot1.SnapshotQualifiedUri); + Assert.AreNotEqual(snapshot1.Uri, snapshot1.SnapshotQualifiedUri); + Assert.IsTrue(snapshot1.SnapshotQualifiedUri.Query.Contains("snapshot")); + + CloudAppendBlob snapshot2 = await blob.CreateSnapshotAsync(); + Assert.IsTrue(snapshot2.SnapshotTime.Value > snapshot1.SnapshotTime.Value); + + await snapshot1.FetchAttributesAsync(); + await snapshot2.FetchAttributesAsync(); + await blob.FetchAttributesAsync(); + AssertAreEqual(snapshot1.Properties, blob.Properties); + + CloudAppendBlob snapshot1Clone = new CloudAppendBlob(new Uri(blob.Uri + "?snapshot=" + snapshot1.SnapshotTime.Value.ToString("O")), blob.ServiceClient.Credentials); + Assert.IsNotNull(snapshot1Clone.SnapshotTime, "Snapshot clone does not have SnapshotTime set"); + Assert.AreEqual(snapshot1.SnapshotTime.Value, snapshot1Clone.SnapshotTime.Value); + await snapshot1Clone.FetchAttributesAsync(); + AssertAreEqual(snapshot1.Properties, snapshot1Clone.Properties, false); + + CloudAppendBlob snapshotCopy = container.GetAppendBlobReference("blob2"); + await snapshotCopy.StartCopyAsync(TestHelper.Defiddler(snapshot1.Uri)); + await WaitForCopyAsync(snapshotCopy); + Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); + + using (Stream snapshotStream = (await snapshot1.OpenReadAsync())) + { + snapshotStream.Seek(0, SeekOrigin.End); + TestHelper.AssertStreamsAreEqual(originalData, snapshotStream); + } + + await blob.CreateOrReplaceAsync(); + + using (Stream snapshotStream = (await snapshot1.OpenReadAsync())) + { + snapshotStream.Seek(0, SeekOrigin.End); + TestHelper.AssertStreamsAreEqual(originalData, snapshotStream); + } + + BlobResultSegment resultSegment = await container.ListBlobsSegmentedAsync(null, true, BlobListingDetails.All, null, null, null, null); + List blobs = resultSegment.Results.ToList(); + Assert.AreEqual(4, blobs.Count); + AssertAreEqual(snapshot1, (CloudBlob)blobs[0]); + AssertAreEqual(snapshot2, (CloudBlob)blobs[1]); + AssertAreEqual(blob, (CloudBlob)blobs[2]); + AssertAreEqual(snapshotCopy, (CloudBlob)blobs[3]); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Create a snapshot with explicit metadata")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobSnapshotMetadataAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + blob.Metadata["Hello"] = "World"; + blob.Metadata["Marco"] = "Polo"; + await blob.SetMetadataAsync(); + + IDictionary snapshotMetadata = new Dictionary(); + snapshotMetadata["Hello"] = "Dolly"; + snapshotMetadata["Yoyo"] = "Ma"; + + CloudAppendBlob snapshot = await blob.CreateSnapshotAsync(snapshotMetadata, null, null, null); + + // Test the client view against the expected metadata + // None of the original metadata should be present + Assert.AreEqual("Dolly", snapshot.Metadata["Hello"]); + Assert.AreEqual("Ma", snapshot.Metadata["Yoyo"]); + Assert.IsFalse(snapshot.Metadata.ContainsKey("Marco")); + + // Test the server view against the expected metadata + await snapshot.FetchAttributesAsync(); + Assert.AreEqual("Dolly", snapshot.Metadata["Hello"]); + Assert.AreEqual("Ma", snapshot.Metadata["Yoyo"]); + Assert.IsFalse(snapshot.Metadata.ContainsKey("Marco")); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Test conditional access on a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobConditionalAccessAsync() + { + OperationContext operationContext = new OperationContext(); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + await blob.FetchAttributesAsync(); + + string currentETag = blob.Properties.ETag; + DateTimeOffset currentModifiedTime = blob.Properties.LastModified.Value; + + // ETag conditional tests + blob.Metadata["ETagConditionalName"] = "ETagConditionalValue"; + await blob.SetMetadataAsync(AccessCondition.GenerateIfMatchCondition(currentETag), null, null); + + await blob.FetchAttributesAsync(); + string newETag = blob.Properties.ETag; + Assert.AreNotEqual(newETag, currentETag, "ETage should be modified on write metadata"); + + blob.Metadata["ETagConditionalName"] = "ETagConditionalValue2"; + + await TestHelper.ExpectedExceptionAsync( + async () => await blob.SetMetadataAsync(AccessCondition.GenerateIfNoneMatchCondition(newETag), null, operationContext), + operationContext, + "If none match on conditional test should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + string invalidETag = "\"0x10101010\""; + await TestHelper.ExpectedExceptionAsync( + async () => await blob.SetMetadataAsync(AccessCondition.GenerateIfMatchCondition(invalidETag), null, operationContext), + operationContext, + "Invalid ETag on conditional test should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + currentETag = blob.Properties.ETag; + await blob.SetMetadataAsync(AccessCondition.GenerateIfNoneMatchCondition(invalidETag), null, null); + + await blob.FetchAttributesAsync(); + newETag = blob.Properties.ETag; + + // LastModifiedTime tests + currentModifiedTime = blob.Properties.LastModified.Value; + + blob.Metadata["DateConditionalName"] = "DateConditionalValue"; + + await TestHelper.ExpectedExceptionAsync( + async () => await blob.SetMetadataAsync(AccessCondition.GenerateIfModifiedSinceCondition(currentModifiedTime), null, operationContext), + operationContext, + "IfModifiedSince conditional on current modified time should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + DateTimeOffset pastTime = currentModifiedTime.Subtract(TimeSpan.FromMinutes(5)); + await blob.SetMetadataAsync(AccessCondition.GenerateIfModifiedSinceCondition(pastTime), null, null); + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromHours(5)); + await blob.SetMetadataAsync(AccessCondition.GenerateIfModifiedSinceCondition(pastTime), null, null); + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromDays(5)); + await blob.SetMetadataAsync(AccessCondition.GenerateIfModifiedSinceCondition(pastTime), null, null); + + currentModifiedTime = blob.Properties.LastModified.Value; + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromMinutes(5)); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.SetMetadataAsync(AccessCondition.GenerateIfNotModifiedSinceCondition(pastTime), null, operationContext), + operationContext, + "IfNotModifiedSince conditional on past time should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromHours(5)); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.SetMetadataAsync(AccessCondition.GenerateIfNotModifiedSinceCondition(pastTime), null, operationContext), + operationContext, + "IfNotModifiedSince conditional on past time should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromDays(5)); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.SetMetadataAsync(AccessCondition.GenerateIfNotModifiedSinceCondition(pastTime), null, operationContext), + operationContext, + "IfNotModifiedSince conditional on past time should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + blob.Metadata["DateConditionalName"] = "DateConditionalValue2"; + + currentETag = blob.Properties.ETag; + await blob.SetMetadataAsync(AccessCondition.GenerateIfNotModifiedSinceCondition(currentModifiedTime), null, null); + + await blob.FetchAttributesAsync(); + newETag = blob.Properties.ETag; + Assert.AreNotEqual(newETag, currentETag, "ETage should be modified on write metadata"); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Test conditional access on a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobAppendOffsetConditionalAccessAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + OperationContext opContext = new OperationContext(); + + byte[] buffer = GetRandomBuffer(1 * 1024 * 1024); + + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + using (MemoryStream originalBlob = new MemoryStream(buffer)) + { + await blob.AppendBlockAsync(originalBlob, null, AccessCondition.GenerateIfAppendPositionEqualCondition(0), null, null); + + // Seek and upload the 1MB again + originalBlob.Seek(0, SeekOrigin.Begin); + await blob.AppendBlockAsync(originalBlob, null, AccessCondition.GenerateIfAppendPositionEqualCondition(1 * 1024 * 1024), null, null); + + // Seek and upload the 1MB again. This time it should throw since the append offset does not match + originalBlob.Seek(0, SeekOrigin.Begin); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.AppendBlockAsync(originalBlob, null, AccessCondition.GenerateIfAppendPositionEqualCondition(1 * 1024 * 1024), null, opContext), + opContext, + "IfAppendPositionEqual conditional should throw", + HttpStatusCode.PreconditionFailed, + "AppendPositionConditionNotMet"); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Test conditional access on a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobMaxSizeConditionalAccessAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + + OperationContext opContext = new OperationContext(); + byte[] buffer = GetRandomBuffer(1 * 1024 * 1024); + + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + AccessCondition condition = AccessCondition.GenerateIfMaxSizeLessThanOrEqualCondition(2 * 1024 * 1024); + using (MemoryStream originalBlob = new MemoryStream(buffer)) + { + await blob.AppendBlockAsync(originalBlob, null, condition, null, null); + + // Seek and upload the 1MB again + originalBlob.Seek(0, SeekOrigin.Begin); + await blob.AppendBlockAsync(originalBlob, null, condition, null, null); + + // Seek and try to upload the 1MB again. This time it should fail with a Pre-condition failed error. + originalBlob.Seek(0, SeekOrigin.Begin); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.AppendBlockAsync(originalBlob, null, condition, null, opContext), + opContext, + "IfMaxSizeLessThanOrEqual conditional should throw", + HttpStatusCode.PreconditionFailed, + "MaxBlobSizeConditionNotMet"); + + originalBlob.Seek(0, SeekOrigin.Begin); + await blob.AppendBlockAsync(originalBlob, null, AccessCondition.GenerateIfMaxSizeLessThanOrEqualCondition(3 * 1024 * 1024), null, null); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Test append blob methods on a block blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobMethodsOnBlockBlobAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + List blobs = await CreateBlobsAsync(container, 1, BlobType.BlockBlob); + CloudAppendBlob blob = container.GetAppendBlobReference(blobs.First()); + + OperationContext operationContext = new OperationContext(); + using (MemoryStream stream = new MemoryStream()) + { + stream.SetLength(512); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.AppendBlockAsync(stream, null, null, null, operationContext), + operationContext, + "Append operations should fail on block blobs", + HttpStatusCode.Conflict, + "InvalidBlobType"); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Test block blob methods on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudBlockBlobMethodsOnAppendBlobAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + List blocks = GetBlockIdList(1); + List blobs = await CreateBlobsAsync(container, 1, BlobType.AppendBlob); + CloudBlockBlob blob = container.GetBlockBlobReference(blobs.First()); + + OperationContext operationContext = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.PutBlockListAsync(blocks, null, null, operationContext), + operationContext, + "Block operations should fail on append blobs", + HttpStatusCode.Conflict, + "InvalidBlobType"); + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Test page blob methods on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudPageBlobMethodsOnAppendBlobAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + List blobs = await CreateBlobsAsync(container, 1, BlobType.AppendBlob); + CloudPageBlob blob = container.GetPageBlobReference(blobs.First()); + + OperationContext operationContext = new OperationContext(); + + using (MemoryStream stream = new MemoryStream()) + { + stream.SetLength(512); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.WritePagesAsync(stream, 0, null, null, null, operationContext), + operationContext, + "Page operations should fail on append blobs", + HttpStatusCode.Conflict, + "InvalidBlobType"); + } + } + finally + { + container.DeleteIfExistsAsync().Wait(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobUploadFromStreamAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + await container.CreateAsync(); + try + { + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, null, null, true, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, null, null, true, 1024); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, null, null, false, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, null, null, false, 1024); + } + finally + { + container.DeleteAsync().Wait(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobUploadFromStreamLengthAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + await container.CreateAsync(); + try + { + // Upload 2MB of 5MB stream + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, true, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, true, 1024); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, false, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, false, 1024); + + // Exclude last byte + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 5 * 1024 * 1024 - 1, null, true, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 4 * 1024 * 1024 - 1, null, true, 1024); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 5 * 1024 * 1024 - 1, null, false, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 4 * 1024 * 1024 - 1, null, false, 1024); + + // Upload exact amount + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 5 * 1024 * 1024, null, true, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 4 * 1024 * 1024, null, true, 1024); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 5 * 1024 * 1024, null, false, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 4 * 1024 * 1024, null, false, 1024); + } + finally + { + container.DeleteAsync().Wait(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobUploadFromStreamInvalidLengthAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + await container.CreateAsync(); + try + { + await TestHelper.ExpectedExceptionAsync( + async () => await this.CloudAppendBlobUploadFromStreamAsync(container, 2 * 1024 * 1024, 2 * 1024 * 1024 + 1, null, true, 0), + "The given stream does not contain the requested number of bytes from its given position."); + + await TestHelper.ExpectedExceptionAsync( + async () => await this.CloudAppendBlobUploadFromStreamAsync(container, 2 * 1024 * 1024, 2 * 1024 * 1024 - 1023, null, true, 1024), + "The given stream does not contain the requested number of bytes from its given position."); + + await TestHelper.ExpectedExceptionAsync( + async () => await this.CloudAppendBlobUploadFromStreamAsync(container, 2 * 1024 * 1024, 2 * 1024 * 1024 + 1, null, false, 0), + "The given stream does not contain the requested number of bytes from its given position."); + + await TestHelper.ExpectedExceptionAsync( + async () => await this.CloudAppendBlobUploadFromStreamAsync(container, 2 * 1024 * 1024, 2 * 1024 * 1024 - 1023, null, false, 1024), + "The given stream does not contain the requested number of bytes from its given position."); + } + finally + { + container.DeleteAsync().Wait(); + } + } + + private async Task CloudAppendBlobUploadFromStreamAsync(CloudBlobContainer container, int size, long? copyLength, AccessCondition accessCondition, bool seekableSourceStream, int startOffset) + { + byte[] buffer = GetRandomBuffer(size); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamWriteSizeInBytes = 1 * 1024 * 1024; + + using (MemoryStream originalBlobStream = new MemoryStream()) + { + originalBlobStream.Write(buffer, startOffset, buffer.Length - startOffset); + + Stream sourceStream; + if (seekableSourceStream) + { + MemoryStream stream = new MemoryStream(buffer); + stream.Seek(startOffset, SeekOrigin.Begin); + sourceStream = stream; + } + else + { + NonSeekableMemoryStream stream = new NonSeekableMemoryStream(buffer); + stream.ForceSeek(startOffset, SeekOrigin.Begin); + sourceStream = stream; + } + + using (sourceStream) + { + if (copyLength.HasValue) + { + await blob.UploadFromStreamAsync(sourceStream, copyLength.Value, accessCondition, null, null); + } + else + { + await blob.UploadFromStreamAsync(sourceStream, accessCondition, null, null); + } + + } + + using (MemoryStream downloadedBlobStream = new MemoryStream()) + { + await blob.DownloadToStreamAsync(downloadedBlobStream); + + Assert.AreEqual(copyLength ?? originalBlobStream.Length, downloadedBlobStream.Length); + TestHelper.AssertStreamsAreEqualAtIndex( + originalBlobStream, + downloadedBlobStream, + 0, + 0, + copyLength.HasValue ? (int)copyLength : (int)originalBlobStream.Length); + } + } + } + } +} diff --git a/Test/WindowsPhoneCommon/Blob/CloudBlobClientTest.cs b/Test/WindowsPhoneCommon/Blob/CloudBlobClientTest.cs index cf81df1e3..058947052 100644 --- a/Test/WindowsPhoneCommon/Blob/CloudBlobClientTest.cs +++ b/Test/WindowsPhoneCommon/Blob/CloudBlobClientTest.cs @@ -94,6 +94,8 @@ public void CloudBlobClientObjects() Assert.AreEqual(blobClient, blockBlob.ServiceClient); CloudPageBlob pageBlob = container.GetPageBlobReference("pageblob"); Assert.AreEqual(blobClient, pageBlob.ServiceClient); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("appendblob"); + Assert.AreEqual(blobClient, appendBlob.ServiceClient); CloudBlobContainer container2 = GetRandomContainerReference(); Assert.AreNotEqual(blobClient, container2.ServiceClient); @@ -101,6 +103,8 @@ public void CloudBlobClientObjects() Assert.AreEqual(container2.ServiceClient, blockBlob2.ServiceClient); CloudPageBlob pageBlob2 = container2.GetPageBlobReference("pageblob"); Assert.AreEqual(container2.ServiceClient, pageBlob2.ServiceClient); + CloudAppendBlob appendBlob2 = container2.GetAppendBlobReference("appendblob"); + Assert.AreEqual(container2.ServiceClient, appendBlob2.ServiceClient); } [TestMethod] diff --git a/Test/WindowsPhoneCommon/Blob/CloudBlobContainerTest.cs b/Test/WindowsPhoneCommon/Blob/CloudBlobContainerTest.cs index d55a977ed..c12e926de 100644 --- a/Test/WindowsPhoneCommon/Blob/CloudBlobContainerTest.cs +++ b/Test/WindowsPhoneCommon/Blob/CloudBlobContainerTest.cs @@ -30,7 +30,6 @@ namespace Microsoft.WindowsAzure.Storage.Blob [TestClass] public class CloudBlobContainerTest : BlobTestBase { - // // Use TestInitialize to run code before running each test [TestInitialize()] public void MyTestInitialize() @@ -51,7 +50,7 @@ public void MyTestCleanup() } } - private static async Task TestAccessAsync(BlobContainerPublicAccessType accessType, CloudBlobContainer container, ICloudBlob inputBlob) + private static async Task TestAccessAsync(BlobContainerPublicAccessType accessType, CloudBlobContainer container, CloudBlob inputBlob) { StorageCredentials credentials = new StorageCredentials(); container = new CloudBlobContainer(container.Uri, credentials); @@ -111,11 +110,13 @@ public void CloudBlobContainerReference() CloudBlobContainer container = client.GetContainerReference("container"); CloudBlockBlob blockBlob = container.GetBlockBlobReference("directory1/blob1"); CloudPageBlob pageBlob = container.GetPageBlobReference("directory2/blob2"); - CloudBlobDirectory directory = container.GetDirectoryReference("directory3"); - CloudBlobDirectory directory2 = directory.GetDirectoryReference("directory4"); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("directory3/blob3"); + CloudBlobDirectory directory = container.GetDirectoryReference("directory4"); + CloudBlobDirectory directory2 = directory.GetDirectoryReference("directory5"); Assert.AreEqual(container, blockBlob.Container); Assert.AreEqual(container, pageBlob.Container); + Assert.AreEqual(container, appendBlob.Container); Assert.AreEqual(container, directory.Container); Assert.AreEqual(container, directory2.Container); Assert.AreEqual(container, directory2.Parent.Container); @@ -514,7 +515,7 @@ public async Task CloudBlobContainerListBlobsWithSecondaryUriAsync() do { BlobResultSegment results = await container.ListBlobsSegmentedAsync(null, true, BlobListingDetails.None, 1, token, null, null); - foreach (ICloudBlob blob in results.Results) + foreach (CloudBlob blob in results.Results) { Assert.IsTrue(blobNames.Remove(blob.Name)); Assert.IsTrue(container.GetBlockBlobReference(blob.Name).StorageUri.Equals(blob.StorageUri)); @@ -557,6 +558,9 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() CloudPageBlob pageBlob = container.GetPageBlobReference("pb"); await pageBlob.CreateAsync(0); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("ab"); + await appendBlob.CreateOrReplaceAsync(); + CloudBlobClient client; ICloudBlob blob; @@ -584,6 +588,18 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlobSnapshot.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = await container.GetBlobReferenceFromServerAsync("ab"); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + + CloudAppendBlob appendBlobSnapshot = await ((CloudAppendBlob)blob).CreateSnapshotAsync(); + await blob.SetPropertiesAsync(); + Uri appendBlobSnapshotUri = new Uri(appendBlobSnapshot.Uri.AbsoluteUri + "?snapshot=" + appendBlobSnapshot.SnapshotTime.Value.UtcDateTime.ToString("o")); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlobSnapshotUri); + AssertAreEqual(appendBlobSnapshot.Properties, blob.Properties); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlobSnapshot.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(blockBlob.Uri); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(blockBlob.Uri)); @@ -594,6 +610,11 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlob.Uri); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(blockBlob.StorageUri, null, null, null); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.Equals(blockBlob.StorageUri)); @@ -602,11 +623,20 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlob.StorageUri, null, null, null); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + string blockBlobToken = blockBlob.GetSharedAccessSignature(policy); StorageCredentials blockBlobSAS = new StorageCredentials(blockBlobToken); Uri blockBlobSASUri = blockBlobSAS.TransformUri(blockBlob.Uri); StorageUri blockBlobSASStorageUri = blockBlobSAS.TransformUri(blockBlob.StorageUri); + string appendBlobToken = appendBlob.GetSharedAccessSignature(policy); + StorageCredentials appendBlobSAS = new StorageCredentials(appendBlobToken); + Uri appendBlobSASUri = appendBlobSAS.TransformUri(appendBlob.Uri); + StorageUri appendBlobSASStorageUri = appendBlobSAS.TransformUri(appendBlob.StorageUri); + string pageBlobToken = pageBlob.GetSharedAccessSignature(policy); StorageCredentials pageBlobSAS = new StorageCredentials(pageBlobToken); Uri pageBlobSASUri = pageBlobSAS.TransformUri(pageBlob.Uri); @@ -622,6 +652,11 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlobSASUri); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(blockBlobSASStorageUri, null, null, null); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.Equals(blockBlob.StorageUri)); @@ -630,6 +665,10 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlobSASStorageUri, null, null, null); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + client = new CloudBlobClient(container.ServiceClient.BaseUri, blockBlobSAS); blob = await client.GetBlobReferenceFromServerAsync(blockBlobSASUri); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); @@ -642,6 +681,12 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + client = new CloudBlobClient(container.ServiceClient.BaseUri, appendBlobSAS); + blob = await client.GetBlobReferenceFromServerAsync(appendBlobSASUri); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + client = new CloudBlobClient(container.ServiceClient.StorageUri, blockBlobSAS); blob = await client.GetBlobReferenceFromServerAsync(blockBlobSASStorageUri, null, null, null); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); @@ -651,6 +696,11 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() blob = await client.GetBlobReferenceFromServerAsync(pageBlobSASStorageUri, null, null, null); Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + + client = new CloudBlobClient(container.ServiceClient.StorageUri, appendBlobSAS); + blob = await client.GetBlobReferenceFromServerAsync(appendBlobSASStorageUri, null, null, null); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); } finally { @@ -658,6 +708,8 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() } } +#pragma warning restore 0618 + [TestMethod] [Description("Test conditional access on a container")] [TestCategory(ComponentCategory.Blob)] diff --git a/Test/WindowsPhoneCommon/Blob/CloudBlobTest.cs b/Test/WindowsPhoneCommon/Blob/CloudBlobTest.cs index aaaf0c7e9..e215f3216 100644 --- a/Test/WindowsPhoneCommon/Blob/CloudBlobTest.cs +++ b/Test/WindowsPhoneCommon/Blob/CloudBlobTest.cs @@ -64,8 +64,10 @@ public async Task CloudBlobSnapshotAsync() await container.CreateAsync(); MemoryStream originalData = new MemoryStream(GetRandomBuffer(1024)); - CloudBlockBlob blockBlob = container.GetBlockBlobReference(BlobName); - await blockBlob.UploadFromStreamAsync(originalData); + + CloudAppendBlob appendBlob = container.GetAppendBlobReference(BlobName); + await appendBlob.CreateOrReplaceAsync(); + await appendBlob.AppendBlockAsync(originalData, null); CloudBlob blob = container.GetBlobReference(BlobName); await blob.FetchAttributesAsync(); @@ -98,6 +100,21 @@ public async Task CloudBlobSnapshotAsync() await snapshot1Clone.FetchAttributesAsync(); AssertAreEqual(snapshot1.Properties, snapshot1Clone.Properties, false); + CloudAppendBlob snapshotCopy = container.GetAppendBlobReference("blob2"); + await snapshotCopy.StartCopyAsync((TestHelper.Defiddler(snapshot1.Uri))); + await WaitForCopyAsync(snapshotCopy); + Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); + + using (Stream snapshotStream = (await snapshot1.OpenReadAsync())) + { + snapshotStream.Seek(0, SeekOrigin.End); + TestHelper.AssertStreamsAreEqual(originalData, snapshotStream); + } + + await appendBlob.CreateOrReplaceAsync(); + await appendBlob.FetchAttributesAsync(); // This is needed as cache settings are not updated with create call above. + await blob.FetchAttributesAsync(); + using (Stream snapshotStream = (await snapshot1.OpenReadAsync())) { snapshotStream.Seek(0, SeekOrigin.End); @@ -106,9 +123,11 @@ public async Task CloudBlobSnapshotAsync() BlobResultSegment resultSegment = await container.ListBlobsSegmentedAsync(null, true, BlobListingDetails.All, null, null, null, null); List blobs = resultSegment.Results.ToList(); - Assert.AreEqual(3, blobs.Count); + Assert.AreEqual(4, blobs.Count); AssertAreEqual(snapshot1, (CloudBlob)blobs[0]); AssertAreEqual(snapshot2, (CloudBlob)blobs[1]); + AssertAreEqual(appendBlob, (CloudBlob)blobs[2]); + AssertAreEqual(snapshotCopy, (CloudBlob)blobs[3]); } finally { @@ -129,8 +148,8 @@ public async Task CloudBlobSnapshotMetadataAsync() { await container.CreateAsync(); - CloudPageBlob pageBlob = container.GetPageBlobReference(BlobName); - await pageBlob.CreateAsync(512); + CloudAppendBlob appendBlob = container.GetAppendBlobReference(BlobName); + await appendBlob.CreateOrReplaceAsync(); CloudBlob blob = container.GetBlobReference(BlobName); blob.Metadata["Hello"] = "World"; diff --git a/Test/WindowsPhoneCommon/Blob/CloudBlockBlobTest.cs b/Test/WindowsPhoneCommon/Blob/CloudBlockBlobTest.cs index 09a094f49..9356f1a51 100644 --- a/Test/WindowsPhoneCommon/Blob/CloudBlockBlobTest.cs +++ b/Test/WindowsPhoneCommon/Blob/CloudBlockBlobTest.cs @@ -821,7 +821,7 @@ public async Task CloudBlockBlobSnapshotAsync() AssertAreEqual(snapshot1.Properties, snapshot1Clone.Properties); CloudBlockBlob snapshotCopy = container.GetBlockBlobReference("blob2"); - await snapshotCopy.StartCopyFromBlobAsync(TestHelper.Defiddler(snapshot1.Uri)); + await snapshotCopy.StartCopyAsync(TestHelper.Defiddler(snapshot1.Uri)); await WaitForCopyAsync(snapshotCopy); Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); diff --git a/Test/WindowsPhoneCommon/Blob/CloudPageBlobTest.cs b/Test/WindowsPhoneCommon/Blob/CloudPageBlobTest.cs index b22eccdd8..dd8b02aee 100644 --- a/Test/WindowsPhoneCommon/Blob/CloudPageBlobTest.cs +++ b/Test/WindowsPhoneCommon/Blob/CloudPageBlobTest.cs @@ -877,7 +877,7 @@ public async Task CloudPageBlobSnapshotAsync() AssertAreEqual(snapshot1.Properties, snapshot1Clone.Properties); CloudPageBlob snapshotCopy = container.GetPageBlobReference("blob2"); - await snapshotCopy.StartCopyFromBlobAsync(TestHelper.Defiddler(snapshot1.Uri)); + await snapshotCopy.StartCopyAsync(TestHelper.Defiddler(snapshot1.Uri)); await WaitForCopyAsync(snapshotCopy); Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); diff --git a/Test/WindowsPhoneCommon/Blob/CopyBlobTest.cs b/Test/WindowsPhoneCommon/Blob/CopyBlobTest.cs index f3a545b21..a4f0c0c41 100644 --- a/Test/WindowsPhoneCommon/Blob/CopyBlobTest.cs +++ b/Test/WindowsPhoneCommon/Blob/CopyBlobTest.cs @@ -71,11 +71,11 @@ public async Task CopyBlobUsingUnicodeBlobNameAsync() //Copy blobs over CloudBlockBlob blobAsciiDest = container.GetBlockBlobReference(_nonUnicodeBlobName + "_copy"); - string copyId = await blobAsciiDest.StartCopyFromBlobAsync(TestHelper.Defiddler(blobAsciiSource)); + string copyId = await blobAsciiDest.StartCopyAsync(TestHelper.Defiddler(blobAsciiSource)); await WaitForCopyAsync(blobAsciiDest); CloudBlockBlob blobUnicodeDest = container.GetBlockBlobReference(_unicodeBlobName + "_copy"); - copyId = await blobUnicodeDest.StartCopyFromBlobAsync(TestHelper.Defiddler(blobUnicodeSource)); + copyId = await blobUnicodeDest.StartCopyAsync(TestHelper.Defiddler(blobUnicodeSource)); await WaitForCopyAsync(blobUnicodeDest); Assert.AreEqual(CopyStatus.Success, blobUnicodeDest.CopyState.Status); @@ -113,7 +113,7 @@ public async Task CloudBlockBlobCopyTestAsync() await source.SetMetadataAsync(); CloudBlockBlob copy = container.GetBlockBlobReference("copy"); - string copyId = await copy.StartCopyFromBlobAsync(TestHelper.Defiddler(source)); + string copyId = await copy.StartCopyAsync(TestHelper.Defiddler(source)); await WaitForCopyAsync(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); Assert.AreEqual(source.Uri.AbsolutePath, copy.CopyState.Source.AbsolutePath); @@ -181,7 +181,7 @@ public async Task CloudBlockBlobCopyTestWithMetadataOverrideAsync() CloudBlockBlob copy = container.GetBlockBlobReference("copy"); copy.Metadata["Test2"] = "value2"; - string copyId = await copy.StartCopyFromBlobAsync(TestHelper.Defiddler(source)); + string copyId = await copy.StartCopyAsync(TestHelper.Defiddler(source)); await WaitForCopyAsync(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); Assert.AreEqual(source.Uri.AbsolutePath, copy.CopyState.Source.AbsolutePath); @@ -253,7 +253,7 @@ public async Task CloudBlockBlobCopyFromSnapshotTestAsync() Assert.AreNotEqual(source.Metadata["Test"], snapshot.Metadata["Test"], "Source and snapshot metadata should be independent"); CloudBlockBlob copy = container.GetBlockBlobReference("copy"); - await copy.StartCopyFromBlobAsync(TestHelper.Defiddler(snapshot)); + await copy.StartCopyAsync(TestHelper.Defiddler(snapshot)); await WaitForCopyAsync(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); Assert.AreEqual(data, await DownloadTextAsync(copy, Encoding.UTF8), "Data inside copy of blob not similar"); @@ -300,7 +300,7 @@ public async Task CloudPageBlobCopyTestAsync() await source.SetMetadataAsync(); CloudPageBlob copy = container.GetPageBlobReference("copy"); - string copyId = await copy.StartCopyFromBlobAsync(TestHelper.Defiddler(source)); + string copyId = await copy.StartCopyAsync(TestHelper.Defiddler(source)); await WaitForCopyAsync(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); Assert.AreEqual(source.Uri.AbsolutePath, copy.CopyState.Source.AbsolutePath); @@ -368,7 +368,7 @@ public async Task CloudPageBlobCopyTestWithMetadataOverrideAsync() CloudPageBlob copy = container.GetPageBlobReference("copy"); copy.Metadata["Test2"] = "value2"; - string copyId = await copy.StartCopyFromBlobAsync(TestHelper.Defiddler(source)); + string copyId = await copy.StartCopyAsync(TestHelper.Defiddler(source)); await WaitForCopyAsync(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); Assert.AreEqual(source.Uri.AbsolutePath, copy.CopyState.Source.AbsolutePath); @@ -440,7 +440,7 @@ public async Task CloudPageBlobCopyFromSnapshotTestAsync() Assert.AreNotEqual(source.Metadata["Test"], snapshot.Metadata["Test"], "Source and snapshot metadata should be independent"); CloudPageBlob copy = container.GetPageBlobReference("copy"); - await copy.StartCopyFromBlobAsync(TestHelper.Defiddler(snapshot)); + await copy.StartCopyAsync(TestHelper.Defiddler(snapshot)); await WaitForCopyAsync(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); Assert.AreEqual(data, await DownloadTextAsync(copy, Encoding.UTF8), "Data inside copy of blob not similar"); diff --git a/Test/WindowsPhoneCommon/Blob/LeaseTests.cs b/Test/WindowsPhoneCommon/Blob/LeaseTests.cs index f6aeb603b..5c5c10608 100644 --- a/Test/WindowsPhoneCommon/Blob/LeaseTests.cs +++ b/Test/WindowsPhoneCommon/Blob/LeaseTests.cs @@ -1227,7 +1227,7 @@ await TestHelper.ExpectedExceptionAsync( expectedStatusCode, expectedErrorCode); await TestHelper.ExpectedExceptionAsync( - async () => await testBlob.StartCopyFromBlobAsync(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */, operationContext), + async () => await testBlob.StartCopyAsync(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */, operationContext), operationContext, description + " (Copy From)", expectedStatusCode, @@ -1257,7 +1257,7 @@ private async Task BlobWriteExpectLeaseSuccessAsync(CloudBlockBlob testBlob, Clo await testBlob.SetMetadataAsync(testAccessCondition, null /* options */, null); await testBlob.SetPropertiesAsync(testAccessCondition, null /* options */, null); await UploadTextAsync(testBlob, "No Problem", Encoding.UTF8, testAccessCondition, null /* options */, null); - await testBlob.StartCopyFromBlobAsync(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */, null); + await testBlob.StartCopyAsync(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */, null); while (testBlob.CopyState.Status == CopyStatus.Pending) { diff --git a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs index 254c6807a..d286cb6bb 100644 --- a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs @@ -24,7 +24,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.4.1.0")] -[assembly: AssemblyFileVersion("4.4.1.0")] -[assembly: AssemblyInformationalVersion("4.4.1.0-preview")] +[assembly: AssemblyVersion("5.0.0.0")] +[assembly: AssemblyFileVersion("5.0.0.0")] [assembly: ComVisible(false)] \ No newline at end of file diff --git a/Test/WindowsRuntime/Blob/BlobReadStreamTest.cs b/Test/WindowsRuntime/Blob/BlobReadStreamTest.cs index 58990bdaf..38e52b501 100644 --- a/Test/WindowsRuntime/Blob/BlobReadStreamTest.cs +++ b/Test/WindowsRuntime/Blob/BlobReadStreamTest.cs @@ -136,6 +136,42 @@ public async Task PageBlobReadStreamBasicTestAsync() } } + [TestMethod] + [Description("Download a blob using CloudBlobStream")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobReadStreamBasicTestAsync() + { + byte[] buffer = GetRandomBuffer(4 * 1024 * 1024); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + await blob.AppendBlockAsync(wholeBlob.AsInputStream()); + } + + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + using (Stream blobStream = (await blob.OpenReadAsync()).AsStreamForRead()) + { + TestHelper.AssertStreamsAreEqual(wholeBlob, blobStream); + } + } + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + [TestMethod] [Description("Modify a blob while downloading it using CloudBlobStream")] [TestCategory(ComponentCategory.Blob)] @@ -262,6 +298,71 @@ await TestHelper.ExpectedExceptionAsync( } } + [TestMethod] + [Description("Modify a blob while downloading it using CloudBlobStream")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobReadLockToETagTestAsync() + { + byte[] outBuffer = new byte[1 * 1024 * 1024]; + byte[] buffer = GetRandomBuffer(2 * outBuffer.Length); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + blob.StreamMinimumReadSizeInBytes = outBuffer.Length; + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + await blob.AppendBlockAsync(wholeBlob.AsInputStream()); + } + + OperationContext opContext = new OperationContext(); + using (var blobStream = await blob.OpenReadAsync(null, null, opContext)) + { + Stream blobStreamForRead = blobStream.AsStreamForRead(); + await blobStreamForRead.ReadAsync(outBuffer, 0, outBuffer.Length); + await blob.SetMetadataAsync(); + await TestHelper.ExpectedExceptionAsync( + async () => await blobStreamForRead.ReadAsync(outBuffer, 0, outBuffer.Length), + opContext, + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + + opContext = new OperationContext(); + using (var blobStream = await blob.OpenReadAsync(null, null, opContext)) + { + Stream blobStreamForRead = blobStream.AsStreamForRead(); + long length = blobStreamForRead.Length; + await blob.SetMetadataAsync(); + await TestHelper.ExpectedExceptionAsync( + async () => await blobStreamForRead.ReadAsync(outBuffer, 0, outBuffer.Length), + opContext, + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + + opContext = new OperationContext(); + AccessCondition accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(DateTimeOffset.Now.Subtract(TimeSpan.FromHours(1))); + await blob.SetMetadataAsync(); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.OpenReadAsync(accessCondition, null, opContext), + opContext, + "Blob read stream should fail if blob is modified during read", + HttpStatusCode.PreconditionFailed); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + #if ASPNET_K private static async Task BlobReadStreamSeekAndCompareAsync(Stream blobStream, byte[] bufferToCompare, ulong offset, uint readSize, uint expectedReadCount) #else @@ -419,5 +520,41 @@ public async Task PageBlobReadStreamSeekTestAsync() container.DeleteIfExistsAsync().AsTask().Wait(); } } + + [TestMethod] + [Description("Seek and read in a CloudBlobStream")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobReadStreamSeekTestAsync() + { + byte[] buffer = GetRandomBuffer(3 * 1024 * 1024); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + blob.StreamMinimumReadSizeInBytes = 2 * 1024 * 1024; + using (MemoryStream wholeBlob = new MemoryStream(buffer)) + { + await blob.AppendBlockAsync(wholeBlob.AsInputStream()); + } + + OperationContext opContext = new OperationContext(); + using (var blobStream = await blob.OpenReadAsync(null, null, opContext)) + { + int attempts = await BlobReadStreamSeekTestAsync(blobStream, blob.StreamMinimumReadSizeInBytes, buffer); + TestHelper.AssertNAttempts(opContext, attempts); + } + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } } } diff --git a/Test/WindowsRuntime/Blob/BlobTestBase.cs b/Test/WindowsRuntime/Blob/BlobTestBase.cs index 811615b06..36def0c0e 100644 --- a/Test/WindowsRuntime/Blob/BlobTestBase.cs +++ b/Test/WindowsRuntime/Blob/BlobTestBase.cs @@ -57,6 +57,13 @@ public static async Task> CreateBlobsAsync(CloudBlobContainer conta await pageBlob.CreateAsync(0); blobs.Add(name); break; + + case BlobType.AppendBlob: + name = "ab" + Guid.NewGuid().ToString(); + CloudAppendBlob appendBlob = container.GetAppendBlobReference(name); + await appendBlob.CreateOrReplaceAsync(); + blobs.Add(name); + break; } } return blobs; @@ -81,7 +88,13 @@ public static async Task UploadTextAsync(CloudBlob blob, string text, Encoding e stream.Seek(0, SeekOrigin.Begin); blob.ServiceClient.DefaultRequestOptions.ParallelOperationThreadCount = 2; - if (blob.BlobType == BlobType.PageBlob) + if (blob.BlobType == BlobType.AppendBlob) + { + CloudAppendBlob blob1 = blob as CloudAppendBlob; + await blob1.CreateOrReplaceAsync(); + await blob1.AppendBlockAsync(stream.AsInputStream(), null); + } + else if (blob.BlobType == BlobType.PageBlob) { CloudPageBlob pageBlob = blob as CloudPageBlob; await pageBlob.UploadFromStreamAsync(stream.AsInputStream(), accessCondition, options, operationContext); diff --git a/Test/WindowsRuntime/Blob/BlobUploadDownloadTest.cs b/Test/WindowsRuntime/Blob/BlobUploadDownloadTest.cs index 5d6f4ff57..01c5965e5 100644 --- a/Test/WindowsRuntime/Blob/BlobUploadDownloadTest.cs +++ b/Test/WindowsRuntime/Blob/BlobUploadDownloadTest.cs @@ -206,6 +206,20 @@ await TestHelper.ExpectedExceptionAsync( "Page blobs must be 512-byte aligned"); } + [TestMethod] + [Description("Upload from file to a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobUploadDownloadFileAsync() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + await this.DoUploadDownloadFileAsync(blob, 0); + await this.DoUploadDownloadFileAsync(blob, 4096); + await this.DoUploadDownloadFileAsync(blob, 4097); + } + private async Task DoUploadDownloadFileAsync(ICloudBlob blob, int fileSize) { #if ASPNET_K @@ -258,9 +272,9 @@ private async Task DoUploadDownloadFileAsync(ICloudBlob blob, int fileSize) await file.WriteAsync(buffer, 0, buffer.Length); } - await blob.UploadFromFileAsync(inputFile); - OperationContext context = new OperationContext(); + + await blob.UploadFromFileAsync(inputFile); await blob.UploadFromFileAsync(inputFile, null, null, context); Assert.IsNotNull(context.LastResult.ServiceRequestID); @@ -317,6 +331,22 @@ await TestHelper.ExpectedExceptionAsync( "Page blobs must be 512-byte aligned"); } + [TestMethod] + [Description("Upload a blob using a byte array")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobUploadFromByteArrayAsync() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + await this.DoUploadFromByteArrayTestAsync(blob, 4 * 512, 0, 4 * 512); + await this.DoUploadFromByteArrayTestAsync(blob, 4 * 512, 0, 2 * 512); + await this.DoUploadFromByteArrayTestAsync(blob, 4 * 512, 1 * 512, 2 * 512); + await this.DoUploadFromByteArrayTestAsync(blob, 4 * 512, 2 * 512, 2 * 512); + await this.DoUploadFromByteArrayTestAsync(blob, 512, 0, 511); + } + private async Task DoUploadFromByteArrayTestAsync(ICloudBlob blob, int bufferSize, int bufferOffset, int count) { byte[] buffer = GetRandomBuffer(bufferSize); @@ -324,6 +354,7 @@ private async Task DoUploadFromByteArrayTestAsync(ICloudBlob blob, int bufferSiz int downloadLength; await blob.UploadFromByteArrayAsync(buffer, bufferOffset, count); + downloadLength = await blob.DownloadToByteArrayAsync(downloadedBuffer, 0); Assert.AreEqual(count, downloadLength); @@ -390,6 +421,34 @@ public async Task CloudPageBlobDownloadToByteArrayAsyncOverload() await this.DoDownloadToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, true); } + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobDownloadToByteArrayAsync() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + await this.DoDownloadToByteArrayAsyncTest(blob, 1 * 512, 2 * 512, 0, false); + await this.DoDownloadToByteArrayAsyncTest(blob, 1 * 512, 2 * 512, 1 * 512, false); + await this.DoDownloadToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, false); + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobDownloadToByteArrayAsyncOverload() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + await this.DoDownloadToByteArrayAsyncTest(blob, 1 * 512, 2 * 512, 0, true); + await this.DoDownloadToByteArrayAsyncTest(blob, 1 * 512, 2 * 512, 1 * 512, true); + await this.DoDownloadToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, true); + } + private async Task DoDownloadToByteArrayAsyncTest(ICloudBlob blob, int blobSize, int bufferSize, int bufferOffset, bool isOverload) { int downloadLength; @@ -399,37 +458,37 @@ private async Task DoDownloadToByteArrayAsyncTest(ICloudBlob blob, int blobSize, using (MemoryStream originalBlob = new MemoryStream(buffer)) { - if (!isOverload) - { - await blob.UploadFromStreamAsync(originalBlob.AsInputStream()); - downloadLength = await blob.DownloadToByteArrayAsync(resultBuffer, bufferOffset); - } - else - { - await blob.UploadFromStreamAsync(originalBlob.AsInputStream()); - OperationContext context = new OperationContext(); - downloadLength = await blob.DownloadToByteArrayAsync(resultBuffer, bufferOffset, null, null, context); - } + await blob.UploadFromStreamAsync(originalBlob.AsInputStream()); + } + + if (!isOverload) + { + downloadLength = await blob.DownloadToByteArrayAsync(resultBuffer, bufferOffset); + } + else + { + OperationContext context = new OperationContext(); + downloadLength = await blob.DownloadToByteArrayAsync(resultBuffer, bufferOffset, null, null, context); + } - int downloadSize = Math.Min(blobSize, bufferSize - bufferOffset); - Assert.AreEqual(downloadSize, downloadLength); + int downloadSize = Math.Min(blobSize, bufferSize - bufferOffset); + Assert.AreEqual(downloadSize, downloadLength); - for (int i = 0; i < blob.Properties.Length; i++) - { - Assert.AreEqual(buffer[i], resultBuffer[bufferOffset + i]); - } + for (int i = 0; i < blob.Properties.Length; i++) + { + Assert.AreEqual(buffer[i], resultBuffer[bufferOffset + i]); + } - for (int j = 0; j < bufferOffset; j++) - { - Assert.AreEqual(0, resultBuffer2[j]); - } + for (int j = 0; j < bufferOffset; j++) + { + Assert.AreEqual(0, resultBuffer2[j]); + } - if (bufferOffset + blobSize < bufferSize) + if (bufferOffset + blobSize < bufferSize) + { + for (int k = bufferOffset + blobSize; k < bufferSize; k++) { - for (int k = bufferOffset + blobSize; k < bufferSize; k++) - { - Assert.AreEqual(0, resultBuffer2[k]); - } + Assert.AreEqual(0, resultBuffer2[k]); } } } @@ -534,6 +593,56 @@ public async Task CloudPageBlobDownloadRangeToByteArrayAsyncOverload() await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 512, 0, 512, true); } + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobDownloadRangeToByteArrayAsync() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 0, 1 * 512, 1 * 512, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, null, null, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, 1 * 512, null, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, 0, 1 * 512, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 1 * 512, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 2 * 512, false); + + // Edge cases + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 1023, 1023, 1, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 0, 1023, 1, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 0, 0, 1, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 0, 512, 1, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 512, 1023, 1, false); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 512, 0, 512, false); + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobDownloadRangeToByteArrayAsyncOverload() + { + CloudAppendBlob blob = this.testContainer.GetAppendBlobReference("blob1"); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 0, 1 * 512, 1 * 512, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, null, null, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, 1 * 512, null, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 1 * 512, 0, 1 * 512, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 1 * 512, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 2 * 512, 4 * 512, 2 * 512, 1 * 512, 2 * 512, true); + + // Edge cases + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 1023, 1023, 1, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 0, 1023, 1, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 0, 0, 1, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 0, 512, 1, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 512, 1023, 1, true); + await this.DoDownloadRangeToByteArrayAsyncTest(blob, 1024, 1024, 512, 0, 512, true); + } + /// /// Single put blob and get blob. /// @@ -552,40 +661,40 @@ private async Task DoDownloadRangeToByteArrayAsyncTest(ICloudBlob blob, int blob using (MemoryStream originalBlob = new MemoryStream(buffer)) { - if (!isOverload) - { - await blob.UploadFromStreamAsync(originalBlob.AsInputStream()); - downloadLength = await blob.DownloadRangeToByteArrayAsync(resultBuffer, bufferOffset, blobOffset, length); - } - else - { - await blob.UploadFromStreamAsync(originalBlob.AsInputStream()); - OperationContext context = new OperationContext(); - downloadLength = await blob.DownloadRangeToByteArrayAsync(resultBuffer, bufferOffset, blobOffset, length, null, null, context); - } + await blob.UploadFromStreamAsync(originalBlob.AsInputStream()); + } - int downloadSize = Math.Min(blobSize - (int)(blobOffset.HasValue ? blobOffset.Value : 0), bufferSize - bufferOffset); - if (length.HasValue && (length.Value < downloadSize)) - { - downloadSize = (int)length.Value; - } + if (!isOverload) + { + downloadLength = await blob.DownloadRangeToByteArrayAsync(resultBuffer, bufferOffset, blobOffset, length); + } + else + { + OperationContext context = new OperationContext(); + downloadLength = await blob.DownloadRangeToByteArrayAsync(resultBuffer, bufferOffset, blobOffset, length, null, null, context); + } - Assert.AreEqual(downloadSize, downloadLength); + int downloadSize = Math.Min(blobSize - (int)(blobOffset.HasValue ? blobOffset.Value : 0), bufferSize - bufferOffset); + if (length.HasValue && (length.Value < downloadSize)) + { + downloadSize = (int)length.Value; + } - for (int i = 0; i < bufferOffset; i++) - { - Assert.AreEqual(0, resultBuffer[i]); - } + Assert.AreEqual(downloadSize, downloadLength); - for (int j = 0; j < downloadLength; j++) - { - Assert.AreEqual(buffer[(blobOffset.HasValue ? blobOffset.Value : 0) + j], resultBuffer[bufferOffset + j]); - } + for (int i = 0; i < bufferOffset; i++) + { + Assert.AreEqual(0, resultBuffer[i]); + } - for (int k = bufferOffset + downloadLength; k < bufferSize; k++) - { - Assert.AreEqual(0, resultBuffer[k]); - } + for (int j = 0; j < downloadLength; j++) + { + Assert.AreEqual(buffer[(blobOffset.HasValue ? blobOffset.Value : 0) + j], resultBuffer[bufferOffset + j]); + } + + for (int k = bufferOffset + downloadLength; k < bufferSize; k++) + { + Assert.AreEqual(0, resultBuffer[k]); } } @@ -623,6 +732,7 @@ private async Task DoDownloadRangeToByteArrayNegativeTestsAsync(ICloudBlob blob) using (MemoryStream stream = new MemoryStream(buffer)) { + await blob.UploadFromStreamAsync(stream.AsInputStream()); OperationContext context = new OperationContext(); diff --git a/Test/WindowsRuntime/Blob/BlobWriteStreamTest.cs b/Test/WindowsRuntime/Blob/BlobWriteStreamTest.cs index 9c259796a..b4a4d65d2 100644 --- a/Test/WindowsRuntime/Blob/BlobWriteStreamTest.cs +++ b/Test/WindowsRuntime/Blob/BlobWriteStreamTest.cs @@ -31,6 +31,7 @@ using Windows.Security.Cryptography; using Windows.Security.Cryptography.Core; using Windows.Storage.Streams; +using Microsoft.WindowsAzure.Storage.Core; #endif namespace Microsoft.WindowsAzure.Storage.Blob @@ -116,6 +117,16 @@ await TestHelper.ExpectedExceptionAsync( await pageBlob2.FetchAttributesAsync(); Assert.AreEqual(1024, pageBlob2.Properties.Length); Assert.AreEqual(BlobType.PageBlob, pageBlob2.Properties.BlobType); + + CloudAppendBlob appendBlob = container.GetAppendBlobReference("blob3"); + using (var writeStream = await appendBlob.OpenWriteAsync(true)) + { + } + + CloudAppendBlob appendBlob2 = container.GetAppendBlobReference("blob3"); + await appendBlob2.FetchAttributesAsync(); + Assert.AreEqual(0, appendBlob2.Properties.Length); + Assert.AreEqual(BlobType.AppendBlob, appendBlob2.Properties.BlobType); } finally { @@ -378,6 +389,109 @@ await TestHelper.ExpectedExceptionAsync( } } + [TestMethod] + [Description("Create a blob using blob stream by specifying an access condition")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobWriteStreamOpenWithAccessConditionAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + await container.CreateAsync(); + + try + { + OperationContext context = new OperationContext(); + + CloudAppendBlob existingBlob = container.GetAppendBlobReference("blob"); + await existingBlob.CreateOrReplaceAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob2"); + AccessCondition accessCondition = AccessCondition.GenerateIfMatchCondition(existingBlob.Properties.ETag); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.OpenWriteAsync(true, accessCondition, null, context), + context, + "OpenWriteAsync with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + blob = container.GetAppendBlobReference("blob3"); + accessCondition = AccessCondition.GenerateIfNoneMatchCondition(existingBlob.Properties.ETag); + var blobStream = await blob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + blob = container.GetAppendBlobReference("blob4"); + accessCondition = AccessCondition.GenerateIfNoneMatchCondition("*"); + blobStream = await blob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + blob = container.GetAppendBlobReference("blob5"); + accessCondition = AccessCondition.GenerateIfModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(1)); + blobStream = await blob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + blob = container.GetAppendBlobReference("blob6"); + accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(-1)); + blobStream = await blob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfMatchCondition(existingBlob.Properties.ETag); + blobStream = await existingBlob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfMatchCondition(blob.Properties.ETag); + await TestHelper.ExpectedExceptionAsync( + async () => await existingBlob.OpenWriteAsync(true, accessCondition, null, context), + context, + "OpenWriteAsync with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + accessCondition = AccessCondition.GenerateIfNoneMatchCondition(blob.Properties.ETag); + blobStream = await existingBlob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfNoneMatchCondition(existingBlob.Properties.ETag); + await TestHelper.ExpectedExceptionAsync( + async () => await existingBlob.OpenWriteAsync(true, accessCondition, null, context), + context, + "OpenWriteAsync with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + accessCondition = AccessCondition.GenerateIfNoneMatchCondition("*"); + await TestHelper.ExpectedExceptionAsync( + async () => await existingBlob.OpenWriteAsync(true, accessCondition, null, context), + context, + "BlobWriteStream.Dispose with a non-met condition should fail", + HttpStatusCode.Conflict); + + accessCondition = AccessCondition.GenerateIfModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(-1)); + blobStream = await existingBlob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(1)); + await TestHelper.ExpectedExceptionAsync( + async () => await existingBlob.OpenWriteAsync(true, accessCondition, null, context), + context, + "OpenWriteAsync with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + + accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(1)); + blobStream = await existingBlob.OpenWriteAsync(true, accessCondition, null, context); + blobStream.Dispose(); + + accessCondition = AccessCondition.GenerateIfNotModifiedSinceCondition(existingBlob.Properties.LastModified.Value.AddMinutes(-1)); + await TestHelper.ExpectedExceptionAsync( + async () => await existingBlob.OpenWriteAsync(true, accessCondition, null, context), + context, + "OpenWriteAsync with a non-met condition should fail", + HttpStatusCode.PreconditionFailed); + } + finally + { + container.DeleteAsync().AsTask().Wait(); + } + } + [TestMethod] [Description("Upload a block blob using blob stream and verify contents")] [TestCategory(ComponentCategory.Blob)] @@ -760,5 +874,224 @@ public async Task PageBlobWriteStreamFlushTestAsync() container.DeleteIfExistsAsync().AsTask().Wait(); } } + + [TestMethod] + [Description("Upload an append blob using blob stream and verify contents")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobWriteStreamBasicTestAsync() + { + byte[] buffer = GetRandomBuffer(3 * 1024 * 1024); + +#if ASPNET_K + MD5 hasher = MD5.Create(); +#else + CryptographicHash hasher = HashAlgorithmProvider.OpenAlgorithm("MD5").CreateHash(); +#endif + CloudBlobContainer container = GetRandomContainerReference(); + + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + + using (MemoryStream wholeBlob = new MemoryStream()) + { + BlobRequestOptions options = new BlobRequestOptions() + { + StoreBlobContentMD5 = true, + }; + + using (var writeStream = await blob.OpenWriteAsync(true, null, options, null)) + { + Stream blobStream = writeStream.AsStreamForWrite(); + + for (int i = 0; i < 3; i++) + { + await blobStream.WriteAsync(buffer, 0, buffer.Length); + await wholeBlob.WriteAsync(buffer, 0, buffer.Length); + Assert.AreEqual(wholeBlob.Position, blobStream.Position); +#if !ASPNET_K + hasher.Append(buffer.AsBuffer()); +#endif + } + + await blobStream.FlushAsync(); + } + +#if ASPNET_K + string md5 = Convert.ToBase64String(hasher.ComputeHash(wholeBlob.ToArray())); +#else + string md5 = CryptographicBuffer.EncodeToBase64String(hasher.GetValueAndReset()); +#endif + await blob.FetchAttributesAsync(); + Assert.AreEqual(md5, blob.Properties.ContentMD5); + + using (MemoryOutputStream downloadedBlob = new MemoryOutputStream()) + { + await blob.DownloadToStreamAsync(downloadedBlob); + TestHelper.AssertStreamsAreEqual(wholeBlob, downloadedBlob.UnderlyingStream); + } + + await TestHelper.ExpectedExceptionAsync( + async () => await blob.OpenWriteAsync(false, null, options, null), + "OpenWrite with StoreBlobContentMD5 on an existing page blob should fail"); + } + } + finally + { + container.DeleteAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Upload a block blob using blob stream and verify contents")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobWriteStreamSeekTestAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + using (var writeStream = await blob.OpenWriteAsync(true)) + { + Stream blobStream = writeStream.AsStreamForWrite(); + + TestHelper.ExpectedException( + () => blobStream.Seek(1, SeekOrigin.Begin), + "Append blob write stream should not be seekable"); + } + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Test the effects of blob stream's flush functionality")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobWriteStreamFlushTestAsync() + { + byte[] buffer = GetRandomBuffer(512 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamWriteSizeInBytes = 1 * 1024 * 1024; + using (MemoryStream wholeBlob = new MemoryStream()) + { + BlobRequestOptions options = new BlobRequestOptions() { StoreBlobContentMD5 = true }; + OperationContext opContext = new OperationContext(); + using (var blobStream = await blob.OpenWriteAsync(true, null, options, opContext)) + { + for (int i = 0; i < 3; i++) + { + await blobStream.WriteAsync(buffer.AsBuffer()); + await wholeBlob.WriteAsync(buffer, 0, buffer.Length); + } + +#if ASPNET_K + // todo: Make some other better logic for this test to be reliable. + System.Threading.Thread.Sleep(500); +#endif + + Assert.AreEqual(2, opContext.RequestResults.Count); + + await blobStream.FlushAsync(); + + Assert.AreEqual(3, opContext.RequestResults.Count); + + await blobStream.FlushAsync(); + + Assert.AreEqual(3, opContext.RequestResults.Count); + + await blobStream.WriteAsync(buffer.AsBuffer()); + await wholeBlob.WriteAsync(buffer, 0, buffer.Length); + + Assert.AreEqual(3, opContext.RequestResults.Count); + + await blobStream.CommitAsync(); + + Assert.AreEqual(5, opContext.RequestResults.Count); + } + + Assert.AreEqual(5, opContext.RequestResults.Count); + + using (MemoryOutputStream downloadedBlob = new MemoryOutputStream()) + { + await blob.DownloadToStreamAsync(downloadedBlob); + TestHelper.AssertStreamsAreEqual(wholeBlob, downloadedBlob.UnderlyingStream); + } + } + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Upload an append blob using blob stream and verify that max conditions is passed through")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.FuntionalTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task AppendBlobWriteStreamMaxSizeConditionTestAsync() + { + byte[] buffer = GetRandomBuffer(16 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamWriteSizeInBytes = 16 * 1024; + OperationContext context = new OperationContext(); + using (MemoryStream wholeBlob = new MemoryStream()) + { + AccessCondition accessCondition = new AccessCondition() { IfMaxSizeLessThanOrEqual = 34 * 1024 }; + try + { + using (var writeStream = await blob.OpenWriteAsync(true, accessCondition, null, context)) + { + Stream blobStream = writeStream.AsStreamForWrite(); + + for (int i = 0; i < 3; i++) + { + await blobStream.WriteAsync(buffer, 0, buffer.Length); + wholeBlob.Write(buffer, 0, buffer.Length); + Assert.AreEqual(wholeBlob.Position, blobStream.Position); + } + } + + Assert.Fail("No exception received while expecting condition failure"); + } + catch (AggregateException ex) + { + Assert.AreEqual(SR.InvalidBlockSize, ex.InnerException.Message); + } + } + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } } } diff --git a/Test/WindowsRuntime/Blob/CloudAppendBlobTest.cs b/Test/WindowsRuntime/Blob/CloudAppendBlobTest.cs new file mode 100644 index 000000000..65d7b85fd --- /dev/null +++ b/Test/WindowsRuntime/Blob/CloudAppendBlobTest.cs @@ -0,0 +1,1213 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- +namespace Microsoft.WindowsAzure.Storage.Blob +{ + using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Threading.Tasks; + +#if ASPNET_K + using System.Security.Cryptography; + using System.Text; +#else + using System.Runtime.InteropServices.WindowsRuntime; + using Windows.Security.Cryptography; + using Windows.Security.Cryptography.Core; + using System.Text; +#endif + + [TestClass] + public class CloudAppendBlobTest : BlobTestBase +#if XUNIT +, IDisposable +#endif + { + +#if XUNIT + // Todo: The simple/nonefficient workaround is to minimize change and support Xunit, + public CloudAppendBlobTest() + { + MyTestInitialize(); + } + public void Dispose() + { + MyTestCleanup(); + } +#endif + + // Use TestInitialize to run code before running each test + [TestInitialize()] + public void MyTestInitialize() + { + if (TestBase.BlobBufferManager != null) + { + TestBase.BlobBufferManager.OutstandingBufferCount = 0; + } + } + + // Use TestCleanup to run code after each test has run + [TestCleanup()] + public void MyTestCleanup() + { + if (TestBase.BlobBufferManager != null) + { + Assert.AreEqual(0, TestBase.BlobBufferManager.OutstandingBufferCount); + } + } + + [TestMethod] + [Description("Create a zero-length append blob and then delete it")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobCreateAndDeleteAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + Assert.IsTrue(await blob.ExistsAsync()); + await blob.DeleteAsync(); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Try to delete a non-existing append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobDeleteIfExistsAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + Assert.IsFalse(await blob.DeleteIfExistsAsync()); + await blob.CreateOrReplaceAsync(); + Assert.IsTrue(await blob.DeleteIfExistsAsync()); + Assert.IsFalse(await blob.DeleteIfExistsAsync()); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Check a blob's existence")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobExistsAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + await container.CreateAsync(); + + try + { + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + + Assert.IsFalse(await blob2.ExistsAsync()); + + await blob.CreateOrReplaceAsync(); + + Assert.IsTrue(await blob2.ExistsAsync()); + Assert.AreEqual(0, blob2.Properties.Length); + + await blob.DeleteAsync(); + + Assert.IsFalse(await blob2.ExistsAsync()); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Verify the attributes of a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobFetchAttributesAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + Assert.AreEqual(0, blob.Properties.Length); + Assert.IsNotNull(blob.Properties.ETag); + Assert.IsTrue(blob.Properties.LastModified > DateTimeOffset.UtcNow.AddMinutes(-5)); + Assert.IsNull(blob.Properties.CacheControl); + Assert.IsNull(blob.Properties.ContentEncoding); + Assert.IsNull(blob.Properties.ContentLanguage); + Assert.IsNull(blob.Properties.ContentType); + Assert.IsNull(blob.Properties.ContentMD5); + Assert.AreEqual(LeaseStatus.Unspecified, blob.Properties.LeaseStatus); + Assert.AreEqual(BlobType.AppendBlob, blob.Properties.BlobType); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + await blob2.FetchAttributesAsync(); + Assert.AreEqual(0, blob2.Properties.Length); + Assert.AreEqual(blob.Properties.ETag, blob2.Properties.ETag); + Assert.AreEqual(blob.Properties.LastModified, blob2.Properties.LastModified); + Assert.IsNull(blob2.Properties.ContentEncoding); + Assert.IsNull(blob2.Properties.ContentLanguage); + Assert.AreEqual("application/octet-stream", blob2.Properties.ContentType); + Assert.IsNull(blob2.Properties.ContentMD5); + Assert.AreEqual(LeaseStatus.Unlocked, blob2.Properties.LeaseStatus); + Assert.AreEqual(BlobType.AppendBlob, blob2.Properties.BlobType); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Verify setting the properties of a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobSetPropertiesAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + string eTag = blob.Properties.ETag; + DateTimeOffset lastModified = blob.Properties.LastModified.Value; + + await Task.Delay(1000); + + blob.Properties.CacheControl = "no-transform"; + blob.Properties.ContentEncoding = "gzip"; + blob.Properties.ContentLanguage = "tr,en"; + blob.Properties.ContentMD5 = "MDAwMDAwMDA="; + blob.Properties.ContentType = "text/html"; + await blob.SetPropertiesAsync(); + Assert.IsTrue(blob.Properties.LastModified > lastModified); + Assert.AreNotEqual(eTag, blob.Properties.ETag); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + await blob2.FetchAttributesAsync(); + Assert.AreEqual("no-transform", blob2.Properties.CacheControl); + Assert.AreEqual("gzip", blob2.Properties.ContentEncoding); + Assert.AreEqual("tr,en", blob2.Properties.ContentLanguage); + Assert.AreEqual("MDAwMDAwMDA=", blob2.Properties.ContentMD5); + Assert.AreEqual("text/html", blob2.Properties.ContentType); + + CloudAppendBlob blob3 = container.GetAppendBlobReference("blob1"); + using (MemoryStream stream = new MemoryStream()) + { + BlobRequestOptions options = new BlobRequestOptions() + { + DisableContentMD5Validation = true, + }; + await blob3.DownloadToStreamAsync(stream.AsOutputStream(), null, options, null); + } + AssertAreEqual(blob2.Properties, blob3.Properties); + + BlobResultSegment results = await container.ListBlobsSegmentedAsync(null); + CloudAppendBlob blob4 = (CloudAppendBlob)results.Results.First(); + AssertAreEqual(blob2.Properties, blob4.Properties); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Try retrieving properties of a block blob using an append blob reference")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobFetchAttributesInvalidTypeAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + CloudBlockBlob blob2 = container.GetBlockBlobReference("blob1"); + OperationContext operationContext = new OperationContext(); + + Assert.ThrowsException( + () => blob2.FetchAttributesAsync(null, null, operationContext).AsTask().Wait(), + "Fetching attributes of an append blob using a block blob reference should fail"); + Assert.IsInstanceOfType(operationContext.LastResult.Exception.InnerException, typeof(InvalidOperationException)); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Verify that creating an append blob can also set its metadata")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobCreateWithMetadataAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.Metadata["key1"] = "value1"; + await blob.CreateOrReplaceAsync(); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + await blob2.FetchAttributesAsync(); + Assert.AreEqual(1, blob2.Metadata.Count); + Assert.AreEqual("value1", blob2.Metadata["key1"]); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Verify that an append blob's metadata can be updated")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobSetMetadataAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + CloudAppendBlob blob2 = container.GetAppendBlobReference("blob1"); + await blob2.FetchAttributesAsync(); + Assert.AreEqual(0, blob2.Metadata.Count); + + OperationContext operationContext = new OperationContext(); + blob.Metadata["key1"] = null; + + Assert.ThrowsException( + () => blob.SetMetadataAsync(null, null, operationContext).AsTask().Wait(), + "Metadata keys should have a non-null value"); + Assert.IsInstanceOfType(operationContext.LastResult.Exception.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = ""; + Assert.ThrowsException( + () => blob.SetMetadataAsync(null, null, operationContext).AsTask().Wait(), + "Metadata keys should have a non-empty value"); + Assert.IsInstanceOfType(operationContext.LastResult.Exception.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = " "; + Assert.ThrowsException( + () => blob.SetMetadataAsync(null, null, operationContext).AsTask().Wait(), + "Metadata keys should have a non-whitespace only value"); + Assert.IsInstanceOfType(operationContext.LastResult.Exception.InnerException, typeof(ArgumentException)); + + blob.Metadata["key1"] = "value1"; + await blob.SetMetadataAsync(); + + await blob2.FetchAttributesAsync(); + Assert.AreEqual(1, blob2.Metadata.Count); + Assert.AreEqual("value1", blob2.Metadata["key1"]); + + BlobResultSegment results = await container.ListBlobsSegmentedAsync(null, true, BlobListingDetails.Metadata, null, null, null, null); + CloudAppendBlob blob3 = (CloudAppendBlob)results.Results.First(); + Assert.AreEqual(1, blob3.Metadata.Count); + Assert.AreEqual("value1", blob3.Metadata["key1"]); + + blob.Metadata.Clear(); + await blob.SetMetadataAsync(); + + await blob2.FetchAttributesAsync(); + Assert.AreEqual(0, blob2.Metadata.Count); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobAppendBlockAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + await container.CreateAsync(); + try + { + await this.CloudAppendBlobUploadFromStreamAsync(container, 6 * 512, null, null, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 6 * 512, null, null, 1024); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + private async Task CloudAppendBlobUploadFromStreamAsync(CloudBlobContainer container, int size, AccessCondition accessCondition, OperationContext operationContext, int startOffset) + { + byte[] buffer = GetRandomBuffer(size); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + using (MemoryStream originalBlobStream = new MemoryStream()) + { + originalBlobStream.Write(buffer, startOffset, buffer.Length - startOffset); + + using (MemoryStream sourceStream = new MemoryStream(buffer)) + { + sourceStream.Seek(startOffset, SeekOrigin.Begin); + await blob.AppendBlockAsync(sourceStream.AsInputStream(), null, accessCondition, null, operationContext); + } + + using (MemoryStream downloadedBlobStream = new MemoryStream()) + { + await blob.DownloadToStreamAsync(downloadedBlobStream.AsOutputStream()); + TestHelper.AssertStreamsAreEqualAtIndex( + originalBlobStream, + downloadedBlobStream, + 0, + 0, + (int)originalBlobStream.Length); + } + } + } + + [TestMethod] + [Description("Upload/Download text")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobDownloadTextAsync() + { + await this.DoTextUploadDownloadAsync("test"); + await this.DoTextUploadDownloadAsync("char中文test"); + } + + private async Task DoTextUploadDownloadAsync(string text) + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateIfNotExistsAsync(); + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + + // Default encoding + await blob.UploadTextAsync(text); + Assert.AreEqual(text, await blob.DownloadTextAsync()); + Assert.AreNotEqual(text, await blob.DownloadTextAsync(Encoding.Unicode, null, null, null)); + + // Custom Encoding + await blob.UploadTextAsync(text, Encoding.Unicode, null, null, null); + Assert.AreEqual(text, await blob.DownloadTextAsync(Encoding.Unicode, null, null, null)); + Assert.AreNotEqual(text, await blob.DownloadTextAsync()); + + // Number of service calls + OperationContext context = new OperationContext(); + await blob.UploadTextAsync(text, null, null, null, context); + + // 3 because of Create and Appendblock. + Assert.AreEqual(2, context.RequestResults.Count); + await blob.DownloadTextAsync(Encoding.Unicode, null, null, context); + Assert.AreEqual(3, context.RequestResults.Count); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Create snapshots of an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobSnapshotAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + MemoryStream originalData = new MemoryStream(GetRandomBuffer(1024)); + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + await blob.AppendBlockAsync(originalData.AsInputStream(), null); + Assert.IsFalse(blob.IsSnapshot); + Assert.IsNull(blob.SnapshotTime, "Root blob has SnapshotTime set"); + Assert.IsFalse(blob.SnapshotQualifiedUri.Query.Contains("snapshot")); + Assert.AreEqual(blob.Uri, blob.SnapshotQualifiedUri); + + CloudAppendBlob snapshot1 = await blob.CreateSnapshotAsync(); + Assert.AreEqual(blob.Properties.ETag, snapshot1.Properties.ETag); + Assert.AreEqual(blob.Properties.LastModified, snapshot1.Properties.LastModified); + Assert.IsTrue(snapshot1.IsSnapshot); + Assert.IsNotNull(snapshot1.SnapshotTime, "Snapshot does not have SnapshotTime set"); + Assert.AreEqual(blob.Uri, snapshot1.Uri); + Assert.AreNotEqual(blob.SnapshotQualifiedUri, snapshot1.SnapshotQualifiedUri); + Assert.AreNotEqual(snapshot1.Uri, snapshot1.SnapshotQualifiedUri); + Assert.IsTrue(snapshot1.SnapshotQualifiedUri.Query.Contains("snapshot")); + + CloudAppendBlob snapshot2 = await blob.CreateSnapshotAsync(); + Assert.IsTrue(snapshot2.SnapshotTime.Value > snapshot1.SnapshotTime.Value); + + await snapshot1.FetchAttributesAsync(); + await snapshot2.FetchAttributesAsync(); + await blob.FetchAttributesAsync(); + AssertAreEqual(snapshot1.Properties, blob.Properties); + + CloudAppendBlob snapshot1Clone = new CloudAppendBlob(new Uri(blob.Uri + "?snapshot=" + snapshot1.SnapshotTime.Value.ToString("O")), blob.ServiceClient.Credentials); + Assert.IsNotNull(snapshot1Clone.SnapshotTime, "Snapshot clone does not have SnapshotTime set"); + Assert.AreEqual(snapshot1.SnapshotTime.Value, snapshot1Clone.SnapshotTime.Value); + await snapshot1Clone.FetchAttributesAsync(); + AssertAreEqual(snapshot1.Properties, snapshot1Clone.Properties, false); + + CloudAppendBlob snapshotCopy = container.GetAppendBlobReference("blob2"); + await snapshotCopy.StartCopyAsync(TestHelper.Defiddler(snapshot1.Uri)); + await WaitForCopyAsync(snapshotCopy); + Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); + + using (Stream snapshotStream = (await snapshot1.OpenReadAsync()).AsStreamForRead()) + { + snapshotStream.Seek(0, SeekOrigin.End); + TestHelper.AssertStreamsAreEqual(originalData, snapshotStream); + } + + await blob.CreateOrReplaceAsync(); + + using (Stream snapshotStream = (await snapshot1.OpenReadAsync()).AsStreamForRead()) + { + snapshotStream.Seek(0, SeekOrigin.End); + TestHelper.AssertStreamsAreEqual(originalData, snapshotStream); + } + + BlobResultSegment resultSegment = await container.ListBlobsSegmentedAsync(null, true, BlobListingDetails.All, null, null, null, null); + List blobs = resultSegment.Results.ToList(); + Assert.AreEqual(4, blobs.Count); + AssertAreEqual(snapshot1, (CloudBlob)blobs[0]); + AssertAreEqual(snapshot2, (CloudBlob)blobs[1]); + AssertAreEqual(blob, (CloudBlob)blobs[2]); + AssertAreEqual(snapshotCopy, (CloudBlob)blobs[3]); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Create a snapshot with explicit metadata")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobSnapshotMetadataAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + blob.Metadata["Hello"] = "World"; + blob.Metadata["Marco"] = "Polo"; + await blob.SetMetadataAsync(); + + IDictionary snapshotMetadata = new Dictionary(); + snapshotMetadata["Hello"] = "Dolly"; + snapshotMetadata["Yoyo"] = "Ma"; + + CloudAppendBlob snapshot = await blob.CreateSnapshotAsync(snapshotMetadata, null, null, null); + + // Test the client view against the expected metadata + // None of the original metadata should be present + Assert.AreEqual("Dolly", snapshot.Metadata["Hello"]); + Assert.AreEqual("Ma", snapshot.Metadata["Yoyo"]); + Assert.IsFalse(snapshot.Metadata.ContainsKey("Marco")); + + // Test the server view against the expected metadata + await snapshot.FetchAttributesAsync(); + Assert.AreEqual("Dolly", snapshot.Metadata["Hello"]); + Assert.AreEqual("Ma", snapshot.Metadata["Yoyo"]); + Assert.IsFalse(snapshot.Metadata.ContainsKey("Marco")); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Test conditional access on a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobConditionalAccessAsync() + { + OperationContext operationContext = new OperationContext(); + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + await blob.FetchAttributesAsync(); + + string currentETag = blob.Properties.ETag; + DateTimeOffset currentModifiedTime = blob.Properties.LastModified.Value; + + // ETag conditional tests + blob.Metadata["ETagConditionalName"] = "ETagConditionalValue"; + await blob.SetMetadataAsync(AccessCondition.GenerateIfMatchCondition(currentETag), null, null); + + await blob.FetchAttributesAsync(); + string newETag = blob.Properties.ETag; + Assert.AreNotEqual(newETag, currentETag, "ETage should be modified on write metadata"); + + blob.Metadata["ETagConditionalName"] = "ETagConditionalValue2"; + + await TestHelper.ExpectedExceptionAsync( + async () => await blob.SetMetadataAsync(AccessCondition.GenerateIfNoneMatchCondition(newETag), null, operationContext), + operationContext, + "If none match on conditional test should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + string invalidETag = "\"0x10101010\""; + await TestHelper.ExpectedExceptionAsync( + async () => await blob.SetMetadataAsync(AccessCondition.GenerateIfMatchCondition(invalidETag), null, operationContext), + operationContext, + "Invalid ETag on conditional test should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + currentETag = blob.Properties.ETag; + await blob.SetMetadataAsync(AccessCondition.GenerateIfNoneMatchCondition(invalidETag), null, null); + + await blob.FetchAttributesAsync(); + newETag = blob.Properties.ETag; + + // LastModifiedTime tests + currentModifiedTime = blob.Properties.LastModified.Value; + + blob.Metadata["DateConditionalName"] = "DateConditionalValue"; + + await TestHelper.ExpectedExceptionAsync( + async () => await blob.SetMetadataAsync(AccessCondition.GenerateIfModifiedSinceCondition(currentModifiedTime), null, operationContext), + operationContext, + "IfModifiedSince conditional on current modified time should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + DateTimeOffset pastTime = currentModifiedTime.Subtract(TimeSpan.FromMinutes(5)); + await blob.SetMetadataAsync(AccessCondition.GenerateIfModifiedSinceCondition(pastTime), null, null); + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromHours(5)); + await blob.SetMetadataAsync(AccessCondition.GenerateIfModifiedSinceCondition(pastTime), null, null); + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromDays(5)); + await blob.SetMetadataAsync(AccessCondition.GenerateIfModifiedSinceCondition(pastTime), null, null); + + currentModifiedTime = blob.Properties.LastModified.Value; + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromMinutes(5)); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.SetMetadataAsync(AccessCondition.GenerateIfNotModifiedSinceCondition(pastTime), null, operationContext), + operationContext, + "IfNotModifiedSince conditional on past time should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromHours(5)); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.SetMetadataAsync(AccessCondition.GenerateIfNotModifiedSinceCondition(pastTime), null, operationContext), + operationContext, + "IfNotModifiedSince conditional on past time should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + pastTime = currentModifiedTime.Subtract(TimeSpan.FromDays(5)); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.SetMetadataAsync(AccessCondition.GenerateIfNotModifiedSinceCondition(pastTime), null, operationContext), + operationContext, + "IfNotModifiedSince conditional on past time should throw", + HttpStatusCode.PreconditionFailed, + "ConditionNotMet"); + + blob.Metadata["DateConditionalName"] = "DateConditionalValue2"; + + currentETag = blob.Properties.ETag; + await blob.SetMetadataAsync(AccessCondition.GenerateIfNotModifiedSinceCondition(currentModifiedTime), null, null); + + await blob.FetchAttributesAsync(); + newETag = blob.Properties.ETag; + Assert.AreNotEqual(newETag, currentETag, "ETage should be modified on write metadata"); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Validate UploadFromStream.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobValidateUploadFromStreamAsync() + { + byte[] buffer = GetRandomBuffer(6 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + + using (MemoryStream memStream = new MemoryStream(buffer)) + { + await blob.UploadFromStreamAsync(memStream.AsInputStream()); + await blob.FetchAttributesAsync(); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + await blob.UploadFromStreamAsync(memStream.AsInputStream()); + await blob.FetchAttributesAsync(); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + await blob.UploadFromStreamAsync(memStream.AsInputStream(), null /* accessCondition */, null /* options */, null /* operationContext */); + await blob.FetchAttributesAsync(); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + } + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Validate AppendFromStream.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobValidateAppendFromStreamAsync() + { + // Every time append a buffer that is bigger than a single block. + byte[] buffer = GetRandomBuffer(6 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + using (MemoryStream memStream = new MemoryStream(buffer)) + { + await blob.AppendFromStreamAsync(memStream.AsInputStream()); + await blob.FetchAttributesAsync(); + Assert.AreEqual(6 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + await blob.AppendFromStreamAsync(memStream.AsInputStream()); + await blob.FetchAttributesAsync(); + Assert.AreEqual(12 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + await blob.AppendFromStreamAsync(memStream.AsInputStream(), null /* accessCondition */, null /* options */, null /* operationContext */); + await blob.FetchAttributesAsync(); + Assert.AreEqual(18 * 1024 * 1024, blob.Properties.Length); + } + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Validate AppendFromStream.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobValidateAppendFromStreamWithLengthAsync() + { + // Every time append a buffer that is bigger than a single block. + byte[] buffer = GetRandomBuffer(6 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + using (MemoryStream memStream = new MemoryStream(buffer)) + { + await blob.AppendFromStreamAsync(memStream.AsInputStream(), 5 * 1024 * 1024); + await blob.FetchAttributesAsync(); + Assert.AreEqual(5 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + await blob.AppendFromStreamAsync(memStream.AsInputStream(), 5 * 1024 * 1024); + await blob.FetchAttributesAsync(); + Assert.AreEqual(10 * 1024 * 1024, blob.Properties.Length); + + memStream.Seek(0, SeekOrigin.Begin); + await blob.AppendFromStreamAsync(memStream.AsInputStream(), 5 * 1024 * 1024, null /* accessCondition */, null /* options */, null /* operationContext */); + await blob.FetchAttributesAsync(); + Assert.AreEqual(15 * 1024 * 1024, blob.Properties.Length); + } + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Verify the append offset returned by the service.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobVerifyAppendOffsetAsync() + { + byte[] buffer = GetRandomBuffer(2 * 1024 * 1024); + + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + long offset = await blob.AppendBlockAsync(new MemoryStream(buffer).AsInputStream()); + Assert.AreEqual(0, offset); + + offset = await blob.AppendBlockAsync(new MemoryStream(buffer).AsInputStream()); + Assert.AreEqual(2 * 1024 * 1024, offset); + + offset = await blob.AppendBlockAsync(new MemoryStream(buffer).AsInputStream()); + Assert.AreEqual(4 * 1024 * 1024, offset); + + offset = await blob.AppendBlockAsync(new MemoryStream(buffer).AsInputStream()); + Assert.AreEqual(6 * 1024 * 1024, offset); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Test conditional access on a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobAppendOffsetConditionalAccessAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + OperationContext opContext = new OperationContext(); + + byte[] buffer = GetRandomBuffer(1 * 1024 * 1024); + + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + using (MemoryStream originalBlob = new MemoryStream(buffer)) + { + await blob.AppendBlockAsync(originalBlob.AsInputStream(), null, AccessCondition.GenerateIfAppendPositionEqualCondition(0), null, null); + + // Seek and upload the 1MB again + originalBlob.Seek(0, SeekOrigin.Begin); + await blob.AppendBlockAsync(originalBlob.AsInputStream(), null, AccessCondition.GenerateIfAppendPositionEqualCondition(1 * 1024 * 1024), null, null); + + // Seek and upload the 1MB again. This time it should throw since the append offset does not match + originalBlob.Seek(0, SeekOrigin.Begin); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.AppendBlockAsync(originalBlob.AsInputStream(), null, AccessCondition.GenerateIfAppendPositionEqualCondition(1 * 1024 * 1024), null, opContext), + opContext, + "IfAppendPositionEqual conditional should throw", + HttpStatusCode.PreconditionFailed, + "AppendPositionConditionNotMet"); + } + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Test conditional access on a blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobMaxSizeConditionalAccessAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + + OperationContext opContext = new OperationContext(); + byte[] buffer = GetRandomBuffer(1 * 1024 * 1024); + + try + { + await container.CreateAsync(); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + await blob.CreateOrReplaceAsync(); + + AccessCondition condition = AccessCondition.GenerateIfMaxSizeLessThanOrEqualCondition(2 * 1024 * 1024); + using (MemoryStream originalBlob = new MemoryStream(buffer)) + { + await blob.AppendBlockAsync(originalBlob.AsInputStream(), null, condition, null, null); + + // Seek and upload the 1MB again + originalBlob.Seek(0, SeekOrigin.Begin); + await blob.AppendBlockAsync(originalBlob.AsInputStream(), null, condition, null, null); + + // Seek and try to upload the 1MB again. This time it should fail with a Pre-condition failed error. + originalBlob.Seek(0, SeekOrigin.Begin); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.AppendBlockAsync(originalBlob.AsInputStream(), null, condition, null, opContext), + opContext, + "IfMaxSizeLessThanOrEqual conditional should throw", + HttpStatusCode.PreconditionFailed, + "MaxBlobSizeConditionNotMet"); + + originalBlob.Seek(0, SeekOrigin.Begin); + await blob.AppendBlockAsync(originalBlob.AsInputStream(), null, AccessCondition.GenerateIfMaxSizeLessThanOrEqualCondition(3 * 1024 * 1024), null, null); + } + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Test append blob methods on a block blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobMethodsOnBlockBlobAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + List blobs = await CreateBlobsAsync(container, 1, BlobType.BlockBlob); + CloudAppendBlob blob = container.GetAppendBlobReference(blobs.First()); + + OperationContext operationContext = new OperationContext(); + using (MemoryStream stream = new MemoryStream()) + { + stream.SetLength(512); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.AppendBlockAsync(stream.AsInputStream(), null, null, null, operationContext), + operationContext, + "Append operations should fail on block blobs", + HttpStatusCode.Conflict, + "InvalidBlobType"); + } + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Test block blob methods on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudBlockBlobMethodsOnAppendBlobAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + List blocks = GetBlockIdList(1); + List blobs = await CreateBlobsAsync(container, 1, BlobType.AppendBlob); + CloudBlockBlob blob = container.GetBlockBlobReference(blobs.First()); + + OperationContext operationContext = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.PutBlockListAsync(blocks, null, null, operationContext), + operationContext, + "Block operations should fail on append blobs", + HttpStatusCode.Conflict, + "InvalidBlobType"); + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Test page blob methods on an append blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudPageBlobMethodsOnAppendBlobAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + try + { + await container.CreateAsync(); + + List blobs = await CreateBlobsAsync(container, 1, BlobType.AppendBlob); + CloudPageBlob blob = container.GetPageBlobReference(blobs.First()); + + OperationContext operationContext = new OperationContext(); + + using (MemoryStream stream = new MemoryStream()) + { + stream.SetLength(512); + await TestHelper.ExpectedExceptionAsync( + async () => await blob.WritePagesAsync(stream.AsInputStream(), 0, null, null, null, operationContext), + operationContext, + "Page operations should fail on append blobs", + HttpStatusCode.Conflict, + "InvalidBlobType"); + } + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobUploadFromStreamAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + await container.CreateAsync(); + try + { + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, null, null, true, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, null, null, true, 1024); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, null, null, false, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, null, null, false, 1024); + } + finally + { + container.DeleteAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobUploadFromStreamLengthAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + await container.CreateAsync(); + try + { + // Upload 2MB of 5MB stream + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, true, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, true, 1024); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, false, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 2 * 1024 * 1024, null, false, 1024); + + // Exclude last byte + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 5 * 1024 * 1024 - 1, null, true, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 4 * 1024 * 1024 - 1, null, true, 1024); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 5 * 1024 * 1024 - 1, null, false, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 4 * 1024 * 1024 - 1, null, false, 1024); + + // Upload exact amount + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 5 * 1024 * 1024, null, true, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 4 * 1024 * 1024, null, true, 1024); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 5 * 1024 * 1024, null, false, 0); + await this.CloudAppendBlobUploadFromStreamAsync(container, 5 * 1024 * 1024, 4 * 1024 * 1024, null, false, 1024); + } + finally + { + container.DeleteAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Single put blob and get blob")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudAppendBlobUploadFromStreamInvalidLengthAsync() + { + CloudBlobContainer container = GetRandomContainerReference(); + await container.CreateAsync(); + try + { + await TestHelper.ExpectedExceptionAsync( + async () => await this.CloudAppendBlobUploadFromStreamAsync(container, 2 * 1024 * 1024, 2 * 1024 * 1024 + 1, null, true, 0), + "The given stream does not contain the requested number of bytes from its given position."); + + await TestHelper.ExpectedExceptionAsync( + async () => await this.CloudAppendBlobUploadFromStreamAsync(container, 2 * 1024 * 1024, 2 * 1024 * 1024 - 1023, null, true, 1024), + "The given stream does not contain the requested number of bytes from its given position."); + + await TestHelper.ExpectedExceptionAsync( + async () => await this.CloudAppendBlobUploadFromStreamAsync(container, 2 * 1024 * 1024, 2 * 1024 * 1024 + 1, null, false, 0), + "The given stream does not contain the requested number of bytes from its given position."); + + await TestHelper.ExpectedExceptionAsync( + async () => await this.CloudAppendBlobUploadFromStreamAsync(container, 2 * 1024 * 1024, 2 * 1024 * 1024 - 1023, null, false, 1024), + "The given stream does not contain the requested number of bytes from its given position."); + } + finally + { + container.DeleteAsync().AsTask().Wait(); + } + } + + private async Task CloudAppendBlobUploadFromStreamAsync(CloudBlobContainer container, int size, long? copyLength, AccessCondition accessCondition, bool seekableSourceStream, int startOffset) + { + byte[] buffer = GetRandomBuffer(size); + + CloudAppendBlob blob = container.GetAppendBlobReference("blob1"); + blob.StreamWriteSizeInBytes = 1 * 1024 * 1024; + + using (MemoryStream originalBlobStream = new MemoryStream()) + { + originalBlobStream.Write(buffer, startOffset, buffer.Length - startOffset); + + Stream sourceStream; + if (seekableSourceStream) + { + MemoryStream stream = new MemoryStream(buffer); + stream.Seek(startOffset, SeekOrigin.Begin); + sourceStream = stream; + } + else + { + NonSeekableMemoryStream stream = new NonSeekableMemoryStream(buffer); + stream.ForceSeek(startOffset, SeekOrigin.Begin); + sourceStream = stream; + } + + using (sourceStream) + { + if (copyLength.HasValue) + { + await blob.UploadFromStreamAsync(sourceStream.AsInputStream(), copyLength.Value, accessCondition, null, null); + } + else + { + await blob.UploadFromStreamAsync(sourceStream.AsInputStream(), accessCondition, null, null); + } + + } + + using (MemoryStream downloadedBlobStream = new MemoryStream()) + { + await blob.DownloadToStreamAsync(downloadedBlobStream.AsOutputStream()); + + Assert.AreEqual(copyLength ?? originalBlobStream.Length, downloadedBlobStream.Length); + TestHelper.AssertStreamsAreEqualAtIndex( + originalBlobStream, + downloadedBlobStream, + 0, + 0, + copyLength.HasValue ? (int)copyLength : (int)originalBlobStream.Length); + } + } + } + } +} diff --git a/Test/WindowsRuntime/Blob/CloudBlobClientTest.cs b/Test/WindowsRuntime/Blob/CloudBlobClientTest.cs index edc7e6ac8..c823c87d9 100644 --- a/Test/WindowsRuntime/Blob/CloudBlobClientTest.cs +++ b/Test/WindowsRuntime/Blob/CloudBlobClientTest.cs @@ -119,6 +119,8 @@ public void CloudBlobClientObjects() Assert.AreEqual(blobClient, blockBlob.ServiceClient); CloudPageBlob pageBlob = container.GetPageBlobReference("pageblob"); Assert.AreEqual(blobClient, pageBlob.ServiceClient); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("appendblob"); + Assert.AreEqual(blobClient, appendBlob.ServiceClient); CloudBlobContainer container2 = GetRandomContainerReference(); Assert.AreNotEqual(blobClient, container2.ServiceClient); @@ -126,6 +128,8 @@ public void CloudBlobClientObjects() Assert.AreEqual(container2.ServiceClient, blockBlob2.ServiceClient); CloudPageBlob pageBlob2 = container2.GetPageBlobReference("pageblob"); Assert.AreEqual(container2.ServiceClient, pageBlob2.ServiceClient); + CloudAppendBlob appendBlob2 = container2.GetAppendBlobReference("appendblob"); + Assert.AreEqual(container2.ServiceClient, appendBlob2.ServiceClient); } [TestMethod] diff --git a/Test/WindowsRuntime/Blob/CloudBlobContainerTest.cs b/Test/WindowsRuntime/Blob/CloudBlobContainerTest.cs index 065de4410..bb36eba29 100644 --- a/Test/WindowsRuntime/Blob/CloudBlobContainerTest.cs +++ b/Test/WindowsRuntime/Blob/CloudBlobContainerTest.cs @@ -72,7 +72,7 @@ public void MyTestCleanup() } } - private static async Task TestAccessAsync(BlobContainerPublicAccessType accessType, CloudBlobContainer container, ICloudBlob inputBlob) + private static async Task TestAccessAsync(BlobContainerPublicAccessType accessType, CloudBlobContainer container, CloudBlob inputBlob) { StorageCredentials credentials = new StorageCredentials(); container = new CloudBlobContainer(container.Uri, credentials); @@ -132,11 +132,13 @@ public void CloudBlobContainerReference() CloudBlobContainer container = client.GetContainerReference("container"); CloudBlockBlob blockBlob = container.GetBlockBlobReference("directory1/blob1"); CloudPageBlob pageBlob = container.GetPageBlobReference("directory2/blob2"); - CloudBlobDirectory directory = container.GetDirectoryReference("directory3"); - CloudBlobDirectory directory2 = directory.GetDirectoryReference("directory4"); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("directory3/blob3"); + CloudBlobDirectory directory = container.GetDirectoryReference("directory4"); + CloudBlobDirectory directory2 = directory.GetDirectoryReference("directory5"); Assert.AreEqual(container, blockBlob.Container); Assert.AreEqual(container, pageBlob.Container); + Assert.AreEqual(container, appendBlob.Container); Assert.AreEqual(container, directory.Container); Assert.AreEqual(container, directory2.Container); Assert.AreEqual(container, directory2.Parent.Container); @@ -544,7 +546,7 @@ public async Task CloudBlobContainerListBlobsWithSecondaryUriAsync() do { BlobResultSegment results = await container.ListBlobsSegmentedAsync(null, true, BlobListingDetails.None, 1, token, null, null); - foreach (ICloudBlob blob in results.Results) + foreach (CloudBlob blob in results.Results) { Assert.IsTrue(blobNames.Remove(blob.Name)); Assert.IsTrue(container.GetBlockBlobReference(blob.Name).StorageUri.Equals(blob.StorageUri)); @@ -587,6 +589,9 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() CloudPageBlob pageBlob = container.GetPageBlobReference("pb"); await pageBlob.CreateAsync(0); + CloudAppendBlob appendBlob = container.GetAppendBlobReference("ab"); + await appendBlob.CreateOrReplaceAsync(); + CloudBlobClient client; ICloudBlob blob; @@ -614,6 +619,18 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlobSnapshot.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = await container.GetBlobReferenceFromServerAsync("ab"); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + + CloudAppendBlob appendBlobSnapshot = await ((CloudAppendBlob)blob).CreateSnapshotAsync(); + await blob.SetPropertiesAsync(); + Uri appendBlobSnapshotUri = new Uri(appendBlobSnapshot.Uri.AbsoluteUri + "?snapshot=" + appendBlobSnapshot.SnapshotTime.Value.UtcDateTime.ToString("o")); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlobSnapshotUri); + AssertAreEqual(appendBlobSnapshot.Properties, blob.Properties); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlobSnapshot.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(blockBlob.Uri); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(blockBlob.Uri)); @@ -624,6 +641,11 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlob.Uri); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(blockBlob.StorageUri, null, null, null); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.Equals(blockBlob.StorageUri)); @@ -632,11 +654,20 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlob.StorageUri, null, null, null); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + string blockBlobToken = blockBlob.GetSharedAccessSignature(policy); StorageCredentials blockBlobSAS = new StorageCredentials(blockBlobToken); Uri blockBlobSASUri = blockBlobSAS.TransformUri(blockBlob.Uri); StorageUri blockBlobSASStorageUri = blockBlobSAS.TransformUri(blockBlob.StorageUri); + string appendBlobToken = appendBlob.GetSharedAccessSignature(policy); + StorageCredentials appendBlobSAS = new StorageCredentials(appendBlobToken); + Uri appendBlobSASUri = appendBlobSAS.TransformUri(appendBlob.Uri); + StorageUri appendBlobSASStorageUri = appendBlobSAS.TransformUri(appendBlob.StorageUri); + string pageBlobToken = pageBlob.GetSharedAccessSignature(policy); StorageCredentials pageBlobSAS = new StorageCredentials(pageBlobToken); Uri pageBlobSASUri = pageBlobSAS.TransformUri(pageBlob.Uri); @@ -652,6 +683,11 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlobSASUri); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(blockBlobSASStorageUri, null, null, null); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); Assert.IsTrue(blob.StorageUri.Equals(blockBlob.StorageUri)); @@ -660,6 +696,10 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + blob = await container.ServiceClient.GetBlobReferenceFromServerAsync(appendBlobSASStorageUri, null, null, null); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); + client = new CloudBlobClient(container.ServiceClient.BaseUri, blockBlobSAS); blob = await client.GetBlobReferenceFromServerAsync(blockBlobSASUri); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); @@ -672,6 +712,12 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(pageBlob.Uri)); Assert.IsNull(blob.StorageUri.SecondaryUri); + client = new CloudBlobClient(container.ServiceClient.BaseUri, appendBlobSAS); + blob = await client.GetBlobReferenceFromServerAsync(appendBlobSASUri); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.PrimaryUri.Equals(appendBlob.Uri)); + Assert.IsNull(blob.StorageUri.SecondaryUri); + client = new CloudBlobClient(container.ServiceClient.StorageUri, blockBlobSAS); blob = await client.GetBlobReferenceFromServerAsync(blockBlobSASStorageUri, null, null, null); Assert.IsInstanceOfType(blob, typeof(CloudBlockBlob)); @@ -681,6 +727,11 @@ public async Task CloudBlobContainerGetBlobReferenceFromServerAsync() blob = await client.GetBlobReferenceFromServerAsync(pageBlobSASStorageUri, null, null, null); Assert.IsInstanceOfType(blob, typeof(CloudPageBlob)); Assert.IsTrue(blob.StorageUri.Equals(pageBlob.StorageUri)); + + client = new CloudBlobClient(container.ServiceClient.StorageUri, appendBlobSAS); + blob = await client.GetBlobReferenceFromServerAsync(appendBlobSASStorageUri, null, null, null); + Assert.IsInstanceOfType(blob, typeof(CloudAppendBlob)); + Assert.IsTrue(blob.StorageUri.Equals(appendBlob.StorageUri)); } finally { diff --git a/Test/WindowsRuntime/Blob/CloudBlobTest.cs b/Test/WindowsRuntime/Blob/CloudBlobTest.cs index dd94759d8..ab6d8c26e 100644 --- a/Test/WindowsRuntime/Blob/CloudBlobTest.cs +++ b/Test/WindowsRuntime/Blob/CloudBlobTest.cs @@ -138,7 +138,7 @@ public async Task CloudBlobSnapshotAsync() AssertAreEqual(snapshot1.Properties, snapshot1Clone.Properties); CloudBlob snapshotCopy = container.GetBlobReference("blob2"); - await snapshotCopy.StartCopyFromBlobAsync(TestHelper.Defiddler(snapshot1.Uri)); + await snapshotCopy.StartCopyAsync(TestHelper.Defiddler(snapshot1.Uri)); await WaitForCopyAsync(snapshotCopy); Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); diff --git a/Test/WindowsRuntime/Blob/CloudBlockBlobTest.cs b/Test/WindowsRuntime/Blob/CloudBlockBlobTest.cs index 05d737c08..b7233d4e6 100644 --- a/Test/WindowsRuntime/Blob/CloudBlockBlobTest.cs +++ b/Test/WindowsRuntime/Blob/CloudBlockBlobTest.cs @@ -989,7 +989,7 @@ public async Task CloudBlockBlobSnapshotAsync() AssertAreEqual(snapshot1.Properties, snapshot1Clone.Properties); CloudBlockBlob snapshotCopy = container.GetBlockBlobReference("blob2"); - await snapshotCopy.StartCopyFromBlobAsync(TestHelper.Defiddler(snapshot1.Uri)); + await snapshotCopy.StartCopyAsync(TestHelper.Defiddler(snapshot1.Uri)); await WaitForCopyAsync(snapshotCopy); Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); diff --git a/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs b/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs index 6ca7c1093..23165dcde 100644 --- a/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs +++ b/Test/WindowsRuntime/Blob/CloudPageBlobTest.cs @@ -953,7 +953,7 @@ public async Task CloudPageBlobSnapshotAsync() AssertAreEqual(snapshot1.Properties, snapshot1Clone.Properties); CloudPageBlob snapshotCopy = container.GetPageBlobReference("blob2"); - await snapshotCopy.StartCopyFromBlobAsync(TestHelper.Defiddler(snapshot1.Uri)); + await snapshotCopy.StartCopyAsync(TestHelper.Defiddler(snapshot1.Uri)); await WaitForCopyAsync(snapshotCopy); Assert.AreEqual(CopyStatus.Success, snapshotCopy.CopyState.Status); diff --git a/Test/WindowsRuntime/Blob/CopyBlobTest.cs b/Test/WindowsRuntime/Blob/CopyBlobTest.cs index 8caa1620b..b60741a91 100644 --- a/Test/WindowsRuntime/Blob/CopyBlobTest.cs +++ b/Test/WindowsRuntime/Blob/CopyBlobTest.cs @@ -86,11 +86,11 @@ public async Task CopyBlobUsingUnicodeBlobNameAsync() //Copy blobs over CloudBlockBlob blobAsciiDest = container.GetBlockBlobReference(_nonUnicodeBlobName + "_copy"); - string copyId = await blobAsciiDest.StartCopyFromBlobAsync(TestHelper.Defiddler(blobAsciiSource)); + string copyId = await blobAsciiDest.StartCopyAsync(TestHelper.Defiddler(blobAsciiSource)); await WaitForCopyAsync(blobAsciiDest); CloudBlockBlob blobUnicodeDest = container.GetBlockBlobReference(_unicodeBlobName + "_copy"); - copyId = await blobUnicodeDest.StartCopyFromBlobAsync(TestHelper.Defiddler(blobUnicodeSource)); + copyId = await blobUnicodeDest.StartCopyAsync(TestHelper.Defiddler(blobUnicodeSource)); await WaitForCopyAsync(blobUnicodeDest); Assert.AreEqual(CopyStatus.Success, blobUnicodeDest.CopyState.Status); @@ -128,7 +128,7 @@ public async Task CloudBlockBlobCopyTestAsync() await source.SetMetadataAsync(); CloudBlockBlob copy = container.GetBlockBlobReference("copy"); - string copyId = await copy.StartCopyFromBlobAsync(TestHelper.Defiddler(source)); + string copyId = await copy.StartCopyAsync(TestHelper.Defiddler(source)); Assert.AreEqual(BlobType.BlockBlob, copy.BlobType); await WaitForCopyAsync(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); @@ -198,7 +198,7 @@ public async Task CloudBlockBlobCopyTestWithMetadataOverrideAsync() CloudBlockBlob copy = container.GetBlockBlobReference("copy"); copy.Metadata["Test2"] = "value2"; - string copyId = await copy.StartCopyFromBlobAsync(TestHelper.Defiddler(source)); + string copyId = await copy.StartCopyAsync(TestHelper.Defiddler(source)); await WaitForCopyAsync(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); Assert.AreEqual(source.Uri.AbsolutePath, copy.CopyState.Source.AbsolutePath); @@ -271,7 +271,7 @@ public async Task CloudBlockBlobCopyFromSnapshotTestAsync() Assert.AreNotEqual(source.Metadata["Test"], snapshot.Metadata["Test"], "Source and snapshot metadata should be independent"); CloudBlockBlob copy = container.GetBlockBlobReference("copy"); - await copy.StartCopyFromBlobAsync(TestHelper.Defiddler(snapshot)); + await copy.StartCopyAsync(TestHelper.Defiddler(snapshot)); await WaitForCopyAsync(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); Assert.AreEqual(data, await DownloadTextAsync(copy, Encoding.UTF8), "Data inside copy of blob not similar"); @@ -319,7 +319,7 @@ public async Task CloudPageBlobCopyTestAsync() await source.SetMetadataAsync(); CloudPageBlob copy = container.GetPageBlobReference("copy"); - string copyId = await copy.StartCopyFromBlobAsync(TestHelper.Defiddler(source)); + string copyId = await copy.StartCopyAsync(TestHelper.Defiddler(source)); Assert.AreEqual(BlobType.PageBlob, copy.BlobType); await WaitForCopyAsync(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); @@ -389,7 +389,7 @@ public async Task CloudPageBlobCopyTestWithMetadataOverrideAsync() CloudPageBlob copy = container.GetPageBlobReference("copy"); copy.Metadata["Test2"] = "value2"; - string copyId = await copy.StartCopyFromBlobAsync(TestHelper.Defiddler(source)); + string copyId = await copy.StartCopyAsync(TestHelper.Defiddler(source)); await WaitForCopyAsync(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); Assert.AreEqual(source.Uri.AbsolutePath, copy.CopyState.Source.AbsolutePath); @@ -462,7 +462,7 @@ public async Task CloudPageBlobCopyFromSnapshotTestAsync() Assert.AreNotEqual(source.Metadata["Test"], snapshot.Metadata["Test"], "Source and snapshot metadata should be independent"); CloudPageBlob copy = container.GetPageBlobReference("copy"); - await copy.StartCopyFromBlobAsync(TestHelper.Defiddler(snapshot)); + await copy.StartCopyAsync(TestHelper.Defiddler(snapshot)); await WaitForCopyAsync(copy); Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); Assert.AreEqual(data, await DownloadTextAsync(copy, Encoding.UTF8), "Data inside copy of blob not similar"); diff --git a/Test/WindowsRuntime/Blob/LeaseTests.cs b/Test/WindowsRuntime/Blob/LeaseTests.cs index 1ff16ab1f..b406d11b7 100644 --- a/Test/WindowsRuntime/Blob/LeaseTests.cs +++ b/Test/WindowsRuntime/Blob/LeaseTests.cs @@ -1246,7 +1246,7 @@ await TestHelper.ExpectedExceptionAsync( expectedStatusCode, expectedErrorCode); await TestHelper.ExpectedExceptionAsync( - async () => await testBlob.StartCopyFromBlobAsync(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */, operationContext), + async () => await testBlob.StartCopyAsync(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */, operationContext), operationContext, description + " (Copy From)", expectedStatusCode, @@ -1277,7 +1277,7 @@ private async Task BlobWriteExpectLeaseSuccessAsync(CloudBlockBlob testBlob, Clo await testBlob.SetMetadataAsync(testAccessCondition, null /* options */, null); await testBlob.SetPropertiesAsync(testAccessCondition, null /* options */, null); await UploadTextAsync(testBlob, "No Problem", Encoding.UTF8, testAccessCondition, null /* options */, null); - await testBlob.StartCopyFromBlobAsync(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */, null); + await testBlob.StartCopyAsync(TestHelper.Defiddler(sourceBlob.Uri), null /* source access condition */, testAccessCondition, null /* options */, null); while (testBlob.CopyState.Status == CopyStatus.Pending) { diff --git a/Test/WindowsRuntime/Blob/MD5FlagsTest.cs b/Test/WindowsRuntime/Blob/MD5FlagsTest.cs index fac24bdbc..3685b5249 100644 --- a/Test/WindowsRuntime/Blob/MD5FlagsTest.cs +++ b/Test/WindowsRuntime/Blob/MD5FlagsTest.cs @@ -52,53 +52,54 @@ public async Task StoreBlobContentMD5TestAsync() { await container.CreateAsync(); - ICloudBlob blob = container.GetBlockBlobReference("blob1"); + CloudBlockBlob blob1 = container.GetBlockBlobReference("blob1"); using (Stream stream = new NonSeekableMemoryStream()) { - await blob.UploadFromStreamAsync(stream.AsInputStream(), null, optionsWithMD5, null); + await blob1.UploadFromStreamAsync(stream.AsInputStream(), null, optionsWithMD5, null); } - await blob.FetchAttributesAsync(); - Assert.IsNotNull(blob.Properties.ContentMD5); + await blob1.FetchAttributesAsync(); + Assert.IsNotNull(blob1.Properties.ContentMD5); - blob = container.GetBlockBlobReference("blob2"); + blob1 = container.GetBlockBlobReference("blob2"); using (Stream stream = new NonSeekableMemoryStream()) { - await blob.UploadFromStreamAsync(stream.AsInputStream(), null, optionsWithNoMD5, null); + await blob1.UploadFromStreamAsync(stream.AsInputStream(), null, optionsWithNoMD5, null); } - await blob.FetchAttributesAsync(); - Assert.IsNull(blob.Properties.ContentMD5); + await blob1.FetchAttributesAsync(); + Assert.IsNull(blob1.Properties.ContentMD5); - blob = container.GetBlockBlobReference("blob3"); + blob1 = container.GetBlockBlobReference("blob3"); using (Stream stream = new NonSeekableMemoryStream()) { - await blob.UploadFromStreamAsync(stream.AsInputStream()); + await blob1.UploadFromStreamAsync(stream.AsInputStream()); } - await blob.FetchAttributesAsync(); - Assert.IsNotNull(blob.Properties.ContentMD5); + await blob1.FetchAttributesAsync(); + Assert.IsNotNull(blob1.Properties.ContentMD5); - blob = container.GetPageBlobReference("blob4"); + CloudPageBlob blob2 = container.GetPageBlobReference("blob4"); + blob2 = container.GetPageBlobReference("blob4"); using (Stream stream = new MemoryStream()) { - await blob.UploadFromStreamAsync(stream.AsInputStream(), null, optionsWithMD5, null); + await blob2.UploadFromStreamAsync(stream.AsInputStream(), null, optionsWithMD5, null); } - await blob.FetchAttributesAsync(); - Assert.IsNotNull(blob.Properties.ContentMD5); + await blob2.FetchAttributesAsync(); + Assert.IsNotNull(blob2.Properties.ContentMD5); - blob = container.GetPageBlobReference("blob5"); + blob2 = container.GetPageBlobReference("blob5"); using (Stream stream = new MemoryStream()) { - await blob.UploadFromStreamAsync(stream.AsInputStream(), null, optionsWithNoMD5, null); + await blob2.UploadFromStreamAsync(stream.AsInputStream(), null, optionsWithNoMD5, null); } - await blob.FetchAttributesAsync(); - Assert.IsNull(blob.Properties.ContentMD5); + await blob2.FetchAttributesAsync(); + Assert.IsNull(blob2.Properties.ContentMD5); - blob = container.GetPageBlobReference("blob6"); + blob2 = container.GetPageBlobReference("blob6"); using (Stream stream = new MemoryStream()) { - await blob.UploadFromStreamAsync(stream.AsInputStream()); + await blob2.UploadFromStreamAsync(stream.AsInputStream()); } - await blob.FetchAttributesAsync(); - Assert.IsNull(blob.Properties.ContentMD5); + await blob2.FetchAttributesAsync(); + Assert.IsNull(blob2.Properties.ContentMD5); } finally { diff --git a/Test/WindowsRuntime/Core/MultiBufferMemoryStreamTests.cs b/Test/WindowsRuntime/Core/MultiBufferMemoryStreamTests.cs index 22f01ea05..bf7cdf2e2 100644 --- a/Test/WindowsRuntime/Core/MultiBufferMemoryStreamTests.cs +++ b/Test/WindowsRuntime/Core/MultiBufferMemoryStreamTests.cs @@ -16,6 +16,7 @@ // ----------------------------------------------------------------------------------------- using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using Microsoft.WindowsAzure.Storage.Blob; using Microsoft.WindowsAzure.Storage.Core.Executor; using Microsoft.WindowsAzure.Storage.Core.Util; using System; @@ -74,5 +75,38 @@ await TestHelper.ExpectedExceptionAsync( TestHelper.AssertStreamsAreEqual(stream1, stream5); } + + [TestMethod] + [Description("Ensure MultiBufferMemoryStream provided by user is not closed after upload.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task EnsureMultiBufferMemoryStreamIsNotClosedAsync() + { + byte[] buffer = GetRandomBuffer(1 * 1024 * 1024); + CloudBlobClient blobClient = GenerateCloudBlobClient(); + CloudBlobContainer container = blobClient.GetContainerReference(Guid.NewGuid().ToString("N")); + + try + { + await container.CreateAsync(); + + CloudBlockBlob blob = container.GetBlockBlobReference("blob1"); + using (MultiBufferMemoryStream originalBlob = new MultiBufferMemoryStream(null)) + { + originalBlob.Write(buffer, 0, buffer.Length); + originalBlob.Seek(0, SeekOrigin.Begin); + + await blob.PutBlockAsync(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), originalBlob.AsInputStream(), null); + + Assert.IsTrue(originalBlob.CanSeek); + } + } + finally + { + container.DeleteIfExistsAsync().AsTask().Wait(); + } + } } } \ No newline at end of file diff --git a/Test/WindowsRuntime/Microsoft.WindowsAzure.StorageRT.Test.csproj b/Test/WindowsRuntime/Microsoft.WindowsAzure.StorageRT.Test.csproj index be9e5d7e5..b31ad8fd6 100644 --- a/Test/WindowsRuntime/Microsoft.WindowsAzure.StorageRT.Test.csproj +++ b/Test/WindowsRuntime/Microsoft.WindowsAzure.StorageRT.Test.csproj @@ -15,7 +15,7 @@ 512 {BC8A1FFA-BEE3-4634-8014-F334798102B3};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} RT_Test_TemporaryKey.pfx - 5E96456B12159A670EAF6D01EC0DEA1921A4BB57 + 13617093D767C01C888FFBD631A1F4745D89DE1B True diff --git a/Test/WindowsRuntime/Package.appxmanifest b/Test/WindowsRuntime/Package.appxmanifest index 64458bde1..520bdcbaf 100644 --- a/Test/WindowsRuntime/Package.appxmanifest +++ b/Test/WindowsRuntime/Package.appxmanifest @@ -17,6 +17,7 @@ + diff --git a/Test/WindowsRuntime/Properties/AssemblyInfo.cs b/Test/WindowsRuntime/Properties/AssemblyInfo.cs index 0a5ee77b5..5f62da2aa 100644 --- a/Test/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Test/WindowsRuntime/Properties/AssemblyInfo.cs @@ -24,7 +24,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.4.1.0")] -[assembly: AssemblyFileVersion("4.4.1.0")] -[assembly: AssemblyInformationalVersion("4.4.1.0-preview")] +[assembly: AssemblyVersion("5.0.0.0")] +[assembly: AssemblyFileVersion("5.0.0.0")] [assembly: ComVisible(false)] diff --git a/Test/WindowsRuntime/RT_Test_TemporaryKey.pfx b/Test/WindowsRuntime/RT_Test_TemporaryKey.pfx index 463b197df..796a86bd8 100644 Binary files a/Test/WindowsRuntime/RT_Test_TemporaryKey.pfx and b/Test/WindowsRuntime/RT_Test_TemporaryKey.pfx differ diff --git a/changelog.txt b/changelog.txt index a9471a6d9..aa02cc1e8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,18 @@ +Changes in 5.0.0 : + + - All: Support for 2015-02-21 REST version. Please see our REST API documentation and blogs for information about the related added features. + - All: Added GA support for Client Side Encryption. For more information about our encryption, please see our documentation and samples. + - All: Deprecated the overload for GetSharedAccessSignature that takes a SAS version because the SAS tokens generated using the current version work fine with old libraries. + - All: Updated the error message for the error that is thrown for having more than 5 shared access policy identifiers to include shares. + - All: Made SyncMemoryStream, a MemoryStream implementation that runs synchronously even when asynchronous overloads are called public. + - All: Updated OData dependencies to v5.6.4 and Newtonsoft.Json dependency to 6.0.8. + - All: Changed behavior to stop stripping out query parameters passed in with the resource URI. Some query parameters such as comp, restype, snapshot and api-version will still be removed. + - Blobs: MultiBufferMemoryStream objects passed as argument to upload methods are not disposed by the client library. + - Blobs: Added CloudAppendBlob class. For more information on this, please refer to the REST API link. + - Blobs: Deprecated StartCopyFromBlob() using the Obsolete attribute. Use StartCopy() instead. + - Blobs: CreateCloudBlobClient does not throw an exception when the credentials are null to support anonymous access. + - Blobs/Files: Fixed a bug which in very rare cases could cause streaming writes to hang. + Changes in 4.4.1 Preview: - All: Refreshed ASP.NET 5 targets to work with latest RC bits (DNX 4.5.1 and DNX Core 5.0, beta4). @@ -10,7 +25,7 @@ Changes in 4.4.0 Preview: - All: Added preview support for client side encryption for blobs, queues and tables. - All: Added preview support for building applications via Portable Class Library (Profile 111), as well as Xamarin.iOS and Xamarin.Android. - All: Added StringToSign to the client logs in the Shared Access Signature case. - - Blobs: Created a CloudBlob instantiable class. Users can now get/set properties, metadata, download blobs, create snapshots, copy blobs and perform leasing actions on blobs without knowing the type of the blob. CloudPageBlob and CloudBlockBlob now derive from this class. + - Blobs: Created a CloudBlob instantiable class. Users can now get/set properties, metadata, download blobs, create snapshots, copy blobs and perform leasing actions on blobs without knowing the type of the blob. CloudPageBlob, CloudBlockBlob and CloudAppendBlob now derive from this class. - Blobs: Added GetBlobReference on CloudBlobContainer to get a reference to a CloudBlob object that can be used for common operations across blob types. This api does not make a service call. - Blobs (ASP.NET): Added missing overloads for StartCopyFromBlob in CloudBlockBlob and CloudPageBlob. - Blobs (Windows Runtime): Added missing custom encoding support for UploadTextAsync() and DownloadTextAsync() API in CloudBlockBlob. diff --git a/global.json b/global.json index 1f316587d..e7fb99ba9 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "projects": [ "lib", "Lib/AspNet" ], "sdk": { - "version": "1.0.0-beta4-11467" + "version": "1.0.0-beta5" } }